Compare commits
815 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1fd484894 | ||
|
|
aeb636cf8a | ||
|
|
4240908c4c | ||
|
|
d53ba64218 | ||
|
|
89ce5a9805 | ||
|
|
1790492f17 | ||
|
|
304cd72eed | ||
|
|
9dba68b34c | ||
|
|
588567b900 | ||
|
|
ef964ab120 | ||
|
|
f3d64b65da | ||
|
|
a9200b2c39 | ||
|
|
209fd33780 | ||
|
|
940c1dbb8e | ||
|
|
5dce68a62f | ||
|
|
9dd8ad1db6 | ||
|
|
0b40f72f0c | ||
|
|
875171e36a | ||
|
|
8e9a20a6c8 | ||
|
|
1a31560374 | ||
|
|
d528b28203 | ||
|
|
c1cb712087 | ||
|
|
ceed5e0b4c | ||
|
|
a449338252 | ||
|
|
9662344e24 | ||
|
|
1536e18784 | ||
|
|
c19431eb20 | ||
|
|
634e38529c | ||
|
|
083ec425d0 | ||
|
|
e8c09db146 | ||
|
|
9ab4d952ef | ||
|
|
48883cce8d | ||
|
|
3d820d39b7 | ||
|
|
4fdca48a97 | ||
|
|
7834ab7979 | ||
|
|
dee808cc57 | ||
|
|
4c23f62a24 | ||
|
|
93633a81a7 | ||
|
|
7465a0e999 | ||
|
|
6aac978754 | ||
|
|
62775f6091 | ||
|
|
97b3725c19 | ||
|
|
46563ee565 | ||
|
|
c68624e048 | ||
|
|
0fa1c5e6ea | ||
|
|
a7fd60987e | ||
|
|
51fe44d53a | ||
|
|
edf67e8e45 | ||
|
|
babfebccef | ||
|
|
ea02d9e919 | ||
|
|
bb3b236cd9 | ||
|
|
1d3fd512ae | ||
|
|
5dbd0ae782 | ||
|
|
b95621758c | ||
|
|
ce51ffae75 | ||
|
|
8cd32138ac | ||
|
|
785c2a6712 | ||
|
|
56a9952546 | ||
|
|
7059a12a25 | ||
|
|
da01254247 | ||
|
|
54a7b943ce | ||
|
|
ec6c843db6 | ||
|
|
7a6bb369d4 | ||
|
|
66a38c8e51 | ||
|
|
f79d2f821e | ||
|
|
72c5e64f74 | ||
|
|
01a44d49b0 | ||
|
|
f0412f5d47 | ||
|
|
2887ee9ac8 | ||
|
|
79d5211db4 | ||
|
|
d05e5d028b | ||
|
|
d3a415ee96 | ||
|
|
db9ac38c64 | ||
|
|
f27d119181 | ||
|
|
f2b85621f5 | ||
|
|
d7bc0aaf4c | ||
|
|
f50e0ca9f9 | ||
|
|
84960b0cef | ||
|
|
42646003fe | ||
|
|
321b8df3a2 | ||
|
|
f1990ff001 | ||
|
|
b48af759f1 | ||
|
|
3969f0414d | ||
|
|
9dc6c60a1a | ||
|
|
4b686a0147 | ||
|
|
f9f8eea6f6 | ||
|
|
3f6dc180cf | ||
|
|
7ce58b55a1 | ||
|
|
e8d1067313 | ||
|
|
c12b0a6dab | ||
|
|
effc3d1c4d | ||
|
|
63a9bd4fbf | ||
|
|
ca829a355c | ||
|
|
8464fc70fe | ||
|
|
dd68f89bf9 | ||
|
|
e42991c066 | ||
|
|
6eb699712d | ||
|
|
1d6662dfe3 | ||
|
|
258be9bf65 | ||
|
|
8592703931 | ||
|
|
44ecc9a203 | ||
|
|
b19e7809ea | ||
|
|
43a0bf4c31 | ||
|
|
546423512a | ||
|
|
b13acb33ed | ||
|
|
82c3193d21 | ||
|
|
503fc37a4b | ||
|
|
1788c9f3c0 | ||
|
|
e1d489a6bc | ||
|
|
9b31e7b66d | ||
|
|
5696c61354 | ||
|
|
b30679c9f9 | ||
|
|
1572410c47 | ||
|
|
f3d5fe08b6 | ||
|
|
247fbfdf01 | ||
|
|
f391f1c719 | ||
|
|
d38854749b | ||
|
|
10a1963801 | ||
|
|
be7e808fff | ||
|
|
ef9a59a96b | ||
|
|
78b6ef60ac | ||
|
|
779a699ff0 | ||
|
|
02e5478c6e | ||
|
|
6520dd33fa | ||
|
|
c479239ab0 | ||
|
|
55c3c527f5 | ||
|
|
8b98597566 | ||
|
|
bfcda30c04 | ||
|
|
c7f0af6902 | ||
|
|
49b09148c6 | ||
|
|
8e2307cbdb | ||
|
|
5978d1ec09 | ||
|
|
c0638e9033 | ||
|
|
ceafe40c65 | ||
|
|
86ee1dd666 | ||
|
|
95be7b1d5b | ||
|
|
7ce022b28e | ||
|
|
dd8233f869 | ||
|
|
947a078161 | ||
|
|
7add78ff80 | ||
|
|
fdf511e283 | ||
|
|
99e491c4fc | ||
|
|
ad84da10c9 | ||
|
|
72a8270084 | ||
|
|
62575092db | ||
|
|
299f4c8afb | ||
|
|
f9eb46a09b | ||
|
|
2970f9f1cb | ||
|
|
ce79774de9 | ||
|
|
cbefbba02c | ||
|
|
3acbcbc690 | ||
|
|
7b086848bf | ||
|
|
77a6d158ea | ||
|
|
28be15ef9f | ||
|
|
96dc7e0998 | ||
|
|
c8f1c98639 | ||
|
|
8c3ba8bac3 | ||
|
|
c563dd5a39 | ||
|
|
bd8cd71452 | ||
|
|
61d0fd9062 | ||
|
|
68709ae65b | ||
|
|
ac1e83ea9f | ||
|
|
6215911719 | ||
|
|
fc81c6e37d | ||
|
|
9d5e0701e7 | ||
|
|
66bdfd8a94 | ||
|
|
935694630b | ||
|
|
f8399fd03c | ||
|
|
429ade5b20 | ||
|
|
5c21dd9852 | ||
|
|
2ce5990f60 | ||
|
|
d56851561b | ||
|
|
5325b5f8b4 | ||
|
|
27936cf3f5 | ||
|
|
ff6f43d7aa | ||
|
|
f1443671ef | ||
|
|
a95697daf8 | ||
|
|
0835744006 | ||
|
|
f5df2fed09 | ||
|
|
5e7f6333b1 | ||
|
|
2aa4ec6a38 | ||
|
|
a70ef4326d | ||
|
|
6edc73784c | ||
|
|
da6e13130b | ||
|
|
4ef4ea8f58 | ||
|
|
4438818f38 | ||
|
|
12eb337444 | ||
|
|
44163f54b1 | ||
|
|
4b282d7813 | ||
|
|
f862a3f975 | ||
|
|
48d5955185 | ||
|
|
dbf5c63ece | ||
|
|
37d2603406 | ||
|
|
9bb1ca2970 | ||
|
|
2abae0ee4c | ||
|
|
9f8355ed7b | ||
|
|
c5bed1c6fb | ||
|
|
1411a92146 | ||
|
|
d016bdd87c | ||
|
|
491ae578ab | ||
|
|
1df685f31e | ||
|
|
02aeb43e62 | ||
|
|
1d849927ef | ||
|
|
1893d258b5 | ||
|
|
ed7f126bd0 | ||
|
|
9a38c4e58d | ||
|
|
99f3998941 | ||
|
|
30fa88e3af | ||
|
|
d951172a81 | ||
|
|
eb35d3a2a0 | ||
|
|
87455fc096 | ||
|
|
054d92cb9c | ||
|
|
36109a1fe7 | ||
|
|
8f8fe9ddad | ||
|
|
2b279e00f9 | ||
|
|
9f5c588bdd | ||
|
|
415b3a1548 | ||
|
|
4e7b8264c3 | ||
|
|
0ef6d1625a | ||
|
|
0258460821 | ||
|
|
2246afcefb | ||
|
|
e0767b1cb7 | ||
|
|
15dd02fe6a | ||
|
|
562a386de7 | ||
|
|
99bea34f14 | ||
|
|
58f1c6ff84 | ||
|
|
b2481d6ba8 | ||
|
|
eaa9b51913 | ||
|
|
6f68980d86 | ||
|
|
9e05c178b4 | ||
|
|
d98808dae9 | ||
|
|
01a749a755 | ||
|
|
c233ba1175 | ||
|
|
b1dc415fb5 | ||
|
|
1242aef466 | ||
|
|
7f2bb6c6ae | ||
|
|
4b1b6098f3 | ||
|
|
53a6acc199 | ||
|
|
bd0a32b07c | ||
|
|
7d480494ba | ||
|
|
5caed66b39 | ||
|
|
136c16bbae | ||
|
|
691dcd269c | ||
|
|
a5cb1f05dc | ||
|
|
4a14db2d4c | ||
|
|
a57c19bb45 | ||
|
|
9fd6ab01de | ||
|
|
48aa11403f | ||
|
|
31b0c3dc04 | ||
|
|
d575c72127 | ||
|
|
d400b9094d | ||
|
|
fda9d29d5e | ||
|
|
d74c05008b | ||
|
|
c8f365ca31 | ||
|
|
66ab3e21c8 | ||
|
|
d4b15a6de4 | ||
|
|
fe61365a11 | ||
|
|
c5047a9301 | ||
|
|
d4f8f2a600 | ||
|
|
d41a5a4874 | ||
|
|
2fe444f903 | ||
|
|
155bf2e873 | ||
|
|
6e282a6d13 | ||
|
|
d3a2e14d4d | ||
|
|
619c0a0f46 | ||
|
|
d4fb0913a4 | ||
|
|
734a15e350 | ||
|
|
d3cbcc769c | ||
|
|
ab43800d8e | ||
|
|
74accb2b7e | ||
|
|
289119833a | ||
|
|
8445d61df0 | ||
|
|
ffb6318475 | ||
|
|
b1a0c3d6a9 | ||
|
|
42edf9750e | ||
|
|
5398826400 | ||
|
|
d4884716e2 | ||
|
|
6712eafefe | ||
|
|
aba6c1a1f4 | ||
|
|
964001fe78 | ||
|
|
449c323fbb | ||
|
|
d290adb0b8 | ||
|
|
ac4e64db6c | ||
|
|
3a0c930113 | ||
|
|
58c94b7279 | ||
|
|
02d25daa4c | ||
|
|
ef57ff7611 | ||
|
|
bed0b3ab1c | ||
|
|
751ba3c62d | ||
|
|
83bc6db74e | ||
|
|
058aa52faf | ||
|
|
2e3950018a | ||
|
|
d86120a257 | ||
|
|
e2fea4a5fb | ||
|
|
1f810f84be | ||
|
|
f6ffc68ef7 | ||
|
|
b3c44f20d7 | ||
|
|
8a5045f635 | ||
|
|
c1607bbd52 | ||
|
|
dfbed6b89d | ||
|
|
c871e55d82 | ||
|
|
f15ac091b4 | ||
|
|
6037d74cfb | ||
|
|
d0e4659d32 | ||
|
|
2d87e692e6 | ||
|
|
f65fb5bc2b | ||
|
|
83597c0efe | ||
|
|
21ee680d3a | ||
|
|
627af50841 | ||
|
|
a955b3730e | ||
|
|
2f7c5b1df4 | ||
|
|
8fca286099 | ||
|
|
8be9701700 | ||
|
|
d66002dc75 | ||
|
|
b12e5270fa | ||
|
|
a717818920 | ||
|
|
d9434a1a9c | ||
|
|
a920a93b3d | ||
|
|
0987a311cf | ||
|
|
a97a6b7fa8 | ||
|
|
414b221727 | ||
|
|
bab2f47ac9 | ||
|
|
0323725543 | ||
|
|
f6a7c8568e | ||
|
|
a21bbfd5a7 | ||
|
|
5f431a1e26 | ||
|
|
bf13b4a917 | ||
|
|
c7ea77d292 | ||
|
|
00d674376e | ||
|
|
afaa52e5e7 | ||
|
|
443cfe7540 | ||
|
|
6768e4f4f7 | ||
|
|
a0e9211b71 | ||
|
|
aeeb3a248a | ||
|
|
3586a2884c | ||
|
|
5e2bdc4e4b | ||
|
|
f2a33b6f6b | ||
|
|
5462526f53 | ||
|
|
4388bbe0a2 | ||
|
|
6f2c09adea | ||
|
|
f9aadbbbdb | ||
|
|
f8c0525512 | ||
|
|
175ffade5e | ||
|
|
cba30fb0b1 | ||
|
|
604b854ef7 | ||
|
|
332843f428 | ||
|
|
cee32f9751 | ||
|
|
cb480706a2 | ||
|
|
4f74267144 | ||
|
|
f6b08e8ed1 | ||
|
|
0faaa3f0c4 | ||
|
|
6acc9300c0 | ||
|
|
70d33fb255 | ||
|
|
c0db592ab5 | ||
|
|
a5c5567936 | ||
|
|
cdd5b66d1b | ||
|
|
b65fffc5f7 | ||
|
|
a3a3e934a1 | ||
|
|
8a4e07e6b2 | ||
|
|
7f4413c79c | ||
|
|
55beaf65a2 | ||
|
|
20fe76ad29 | ||
|
|
0d5bc753ca | ||
|
|
79507a4b03 | ||
|
|
3a1be04a36 | ||
|
|
ec523e5235 | ||
|
|
c7b1aa2948 | ||
|
|
9370383f64 | ||
|
|
9856c03566 | ||
|
|
ec686bcaa5 | ||
|
|
06291a15a6 | ||
|
|
3e40a6f5c6 | ||
|
|
f91b48e214 | ||
|
|
e340f405b4 | ||
|
|
24fc39b016 | ||
|
|
2fc3f3c00e | ||
|
|
21e0faeb1e | ||
|
|
4b71a8d5f4 | ||
|
|
4075965252 | ||
|
|
3538312769 | ||
|
|
2924f36970 | ||
|
|
bf16dfa09c | ||
|
|
910e86d6a2 | ||
|
|
537fea103e | ||
|
|
87a6e2709b | ||
|
|
d704e78223 | ||
|
|
8ceabd513c | ||
|
|
4806c69053 | ||
|
|
c9f333003b | ||
|
|
33fa32cb07 | ||
|
|
5d1377af80 | ||
|
|
a75e239fd2 | ||
|
|
a663482dc6 | ||
|
|
e8d6235529 | ||
|
|
d8677d8a6d | ||
|
|
351f66a83e | ||
|
|
79c7c01964 | ||
|
|
f51474b66d | ||
|
|
14f01905d7 | ||
|
|
23eb31e6a2 | ||
|
|
e8d4f00f49 | ||
|
|
978e3f945d | ||
|
|
072f394476 | ||
|
|
5926d7037d | ||
|
|
52b51ee7d0 | ||
|
|
54f831ede4 | ||
|
|
c9ead63ff6 | ||
|
|
9ca74cf8d2 | ||
|
|
2a0d455419 | ||
|
|
9e959ca455 | ||
|
|
d3f6796bb9 | ||
|
|
64d6eeabcb | ||
|
|
859258c25c | ||
|
|
2338255a54 | ||
|
|
843afa93c3 | ||
|
|
5441e63825 | ||
|
|
7bf741027e | ||
|
|
0017e2fcc8 | ||
|
|
5c9c8b4cb7 | ||
|
|
873d0ea2a3 | ||
|
|
db4987f557 | ||
|
|
b0b61f886d | ||
|
|
5d4b25f417 | ||
|
|
c88d7c880b | ||
|
|
a96d13978b | ||
|
|
22b26de78d | ||
|
|
53b57879a3 | ||
|
|
d794cef770 | ||
|
|
19f175ba89 | ||
|
|
aaf105ef51 | ||
|
|
974308f939 | ||
|
|
0ec738a48a | ||
|
|
904f6e22f4 | ||
|
|
66fad96e61 | ||
|
|
9037930e56 | ||
|
|
d62013177d | ||
|
|
fc08ede58a | ||
|
|
995a62985e | ||
|
|
67d5a00a0c | ||
|
|
84364c9df2 | ||
|
|
446eb9f9a4 | ||
|
|
a73e814167 | ||
|
|
7125562c9d | ||
|
|
1361f99639 | ||
|
|
d70a87a89a | ||
|
|
2cb0d9527e | ||
|
|
1839e145ec | ||
|
|
8c1e41a3a8 | ||
|
|
9859c0b4dd | ||
|
|
1138101dd0 | ||
|
|
106ac7ea35 | ||
|
|
19b4ee463b | ||
|
|
2aea96c3ba | ||
|
|
b1fb499e29 | ||
|
|
64207f29b0 | ||
|
|
7b6ac6cfaa | ||
|
|
8a9ade5f12 | ||
|
|
a3e567bc8a | ||
|
|
a887666bf5 | ||
|
|
ed8aa08255 | ||
|
|
53f4fb9ede | ||
|
|
bb1ccb7f1a | ||
|
|
03fe90ad53 | ||
|
|
1601962f03 | ||
|
|
ac8e59acba | ||
|
|
5f6d17bfe2 | ||
|
|
3c5fe00c30 | ||
|
|
91754848af | ||
|
|
d8eb4d188a | ||
|
|
25d64911d4 | ||
|
|
888f02dbaa | ||
|
|
728a5653be | ||
|
|
a5dc0fddb8 | ||
|
|
134f30e81f | ||
|
|
9b07ac2be4 | ||
|
|
571a49ba6f | ||
|
|
077437e1f1 | ||
|
|
ba9e509d67 | ||
|
|
181ab149e3 | ||
|
|
5ef64c7dd1 | ||
|
|
0f6a675d0c | ||
|
|
ec3552bbde | ||
|
|
5783602694 | ||
|
|
9ba76992e4 | ||
|
|
d075ab6a74 | ||
|
|
4cdfeaae34 | ||
|
|
e54b46910a | ||
|
|
9558406c90 | ||
|
|
f735c34841 | ||
|
|
1aa4afad5f | ||
|
|
58005d81d6 | ||
|
|
b0381c58e6 | ||
|
|
99a3cfafc9 | ||
|
|
6c908eb710 | ||
|
|
dc15144a29 | ||
|
|
34431bcb75 | ||
|
|
0041b787ec | ||
|
|
868edc6028 | ||
|
|
d8c84a91f4 | ||
|
|
68282af457 | ||
|
|
0ecb732331 | ||
|
|
86785c726a | ||
|
|
10fe7f5213 | ||
|
|
4deab7554c | ||
|
|
b92c8007e4 | ||
|
|
029f3acbd5 | ||
|
|
0b9fda5b25 | ||
|
|
47816f2530 | ||
|
|
73023499aa | ||
|
|
241043b616 | ||
|
|
e97e117298 | ||
|
|
b63e95388c | ||
|
|
ef8fba1d49 | ||
|
|
be64c03d06 | ||
|
|
45682ca59e | ||
|
|
397b9d423e | ||
|
|
d9113fb6d4 | ||
|
|
5dc0df2132 | ||
|
|
7f2cc9a3e6 | ||
|
|
d3c4ca6e09 | ||
|
|
28538214d2 | ||
|
|
4f48f3372c | ||
|
|
9e070107a2 | ||
|
|
2a92b2992f | ||
|
|
e180153c3a | ||
|
|
e35f9e16a1 | ||
|
|
8e955e9257 | ||
|
|
9ca44652b3 | ||
|
|
3d89d72426 | ||
|
|
c2ffab3273 | ||
|
|
dbb62b613c | ||
|
|
6f2d6e31c9 | ||
|
|
944939a2e9 | ||
|
|
04164a550d | ||
|
|
16a5e15070 | ||
|
|
ee97d8d902 | ||
|
|
3d4b077b89 | ||
|
|
0f4a89d938 | ||
|
|
0d0ec12dbf | ||
|
|
afe30a2c02 | ||
|
|
2f9289a2c5 | ||
|
|
7a9d2e80d0 | ||
|
|
d4b8735c04 | ||
|
|
d31687d0bf | ||
|
|
470535dd33 | ||
|
|
27f53f1e99 | ||
|
|
e7d498c95e | ||
|
|
02b617d297 | ||
|
|
96d04e1a7d | ||
|
|
fe9d805d7c | ||
|
|
b6c136839e | ||
|
|
8c947a0e0d | ||
|
|
49c5cbbf3b | ||
|
|
efaefe2e44 | ||
|
|
ed95f1b966 | ||
|
|
7ecbc552aa | ||
|
|
dadc997398 | ||
|
|
b90d6b5ab0 | ||
|
|
db5c6f82fd | ||
|
|
f161f9617b | ||
|
|
9d3249e6bd | ||
|
|
f1c65afa98 | ||
|
|
80c1f0cdf9 | ||
|
|
ea0fe83d00 | ||
|
|
994836fba7 | ||
|
|
41aa1dcb0b | ||
|
|
3965d3fdd9 | ||
|
|
ff6fd8a6e9 | ||
|
|
0a6906b23e | ||
|
|
c38b5c0ce3 | ||
|
|
86f3b5a036 | ||
|
|
382a44f040 | ||
|
|
2d9604cd14 | ||
|
|
7930709bc3 | ||
|
|
6fe2c92cee | ||
|
|
884dee2db3 | ||
|
|
1f00c2a9c4 | ||
|
|
84070835df | ||
|
|
fb389137e7 | ||
|
|
c14b6a78ae | ||
|
|
a6c1c49f98 | ||
|
|
7848ad86bd | ||
|
|
882c15dada | ||
|
|
704ad222d9 | ||
|
|
cfee80ce3c | ||
|
|
eb95cedd72 | ||
|
|
99a7fb3383 | ||
|
|
7f4dbf9346 | ||
|
|
09b00c9974 | ||
|
|
b74fb8174d | ||
|
|
a7ec5a64b7 | ||
|
|
c619dcf65d | ||
|
|
1221d88e13 | ||
|
|
48d2ead71d | ||
|
|
ed7099bfd6 | ||
|
|
2604cdfdae | ||
|
|
94dc389812 | ||
|
|
4a8c621f46 | ||
|
|
477af8af4e | ||
|
|
a9a7d524aa | ||
|
|
1cdaff8ddf | ||
|
|
eeea3d2dcc | ||
|
|
f46b6a0697 | ||
|
|
622581ee0a | ||
|
|
eb725f51ce | ||
|
|
4d4462368b | ||
|
|
6fe5d4363d | ||
|
|
b46fa27084 | ||
|
|
ade2440613 | ||
|
|
4dc4620b60 | ||
|
|
202c2662f1 | ||
|
|
d2afd71c81 | ||
|
|
bad1ce2618 | ||
|
|
f194845b2b | ||
|
|
98d4e108e6 | ||
|
|
ab43ed1900 | ||
|
|
b147e047a5 | ||
|
|
bbb616f56c | ||
|
|
35a429ea65 | ||
|
|
bd16543c10 | ||
|
|
cbdeae276e | ||
|
|
e733113963 | ||
|
|
272b030b89 | ||
|
|
472a553d13 | ||
|
|
3a1bff146c | ||
|
|
b82b9acc54 | ||
|
|
fac3845756 | ||
|
|
9381a74f1d | ||
|
|
f6662a3208 | ||
|
|
134af82d90 | ||
|
|
75175b8e54 | ||
|
|
e3d8599dc7 | ||
|
|
77ee69ad52 | ||
|
|
4b82838f80 | ||
|
|
a177829976 | ||
|
|
b8b4f960dd | ||
|
|
2466414307 | ||
|
|
a3a32f0d68 | ||
|
|
ee001f86f0 | ||
|
|
87d6975bd8 | ||
|
|
4cab4e27ff | ||
|
|
f105f0995b | ||
|
|
18e89a7353 | ||
|
|
445f9a67a4 | ||
|
|
4c039c900c | ||
|
|
2c2e0a3419 | ||
|
|
1b43e0b28a | ||
|
|
a23f7782b2 | ||
|
|
723db9d0ea | ||
|
|
cbf165dc40 | ||
|
|
6f3579acf1 | ||
|
|
c9e2f86e57 | ||
|
|
8c0199119e | ||
|
|
5e6e6be888 | ||
|
|
79fb6485b1 | ||
|
|
29baaaf2ed | ||
|
|
f824fb25c2 | ||
|
|
28a6ea7e1a | ||
|
|
65566faf17 | ||
|
|
eb4f90e669 | ||
|
|
40308158ca | ||
|
|
d0d5240474 | ||
|
|
751ce8e6d6 | ||
|
|
748a5eadc0 | ||
|
|
5659fe21ea | ||
|
|
bc899b74a3 | ||
|
|
516c8b05a4 | ||
|
|
8502f9e303 | ||
|
|
bc90d416e6 | ||
|
|
2fd1cd72fd | ||
|
|
3a2276dcd1 | ||
|
|
c432c583f6 | ||
|
|
9b8f5ed8f4 | ||
|
|
0713a15028 | ||
|
|
62c502615f | ||
|
|
fdbe4b8f5e | ||
|
|
33c73f4dc8 | ||
|
|
c207a576e7 | ||
|
|
aa426514f3 | ||
|
|
ba9c28892e | ||
|
|
49c31ddfc8 | ||
|
|
4424205814 | ||
|
|
874f3fb737 | ||
|
|
ac27c89a44 | ||
|
|
0e530cf92e | ||
|
|
ed66b00b20 | ||
|
|
84fb05239b | ||
|
|
7500bbc0be | ||
|
|
c43c8de6cd | ||
|
|
9d40db5b21 | ||
|
|
a61eca36ae | ||
|
|
60479a442f | ||
|
|
5257188f70 | ||
|
|
7599239983 | ||
|
|
1561c0d000 | ||
|
|
1f740cf8c0 | ||
|
|
55c5f03004 | ||
|
|
a345cff614 | ||
|
|
85d8566cfa | ||
|
|
44d68e3ef0 | ||
|
|
04bd4900b0 | ||
|
|
81f277b36f | ||
|
|
575660d3e8 | ||
|
|
1e692491f1 | ||
|
|
b3639b3453 | ||
|
|
53ebf37b40 | ||
|
|
624ec33652 | ||
|
|
2890040118 | ||
|
|
68db8b3e23 | ||
|
|
9873491db1 | ||
|
|
5d64ab0146 | ||
|
|
568267ce07 | ||
|
|
69cdefe996 | ||
|
|
61758db589 | ||
|
|
9e9d98bb31 | ||
|
|
6b2b771dc4 | ||
|
|
031bea0f50 | ||
|
|
6f2e447ec3 | ||
|
|
cf831fbcea | ||
|
|
bcd4c1c182 | ||
|
|
d954026cea | ||
|
|
2c2dad1415 | ||
|
|
d42ed88845 | ||
|
|
e12c00e980 | ||
|
|
c25971e5d2 | ||
|
|
293e56758c | ||
|
|
a6b17f0437 | ||
|
|
140d59b898 | ||
|
|
85742c5785 | ||
|
|
9b07d8ce64 | ||
|
|
b1b9da1b17 | ||
|
|
ffe670923e | ||
|
|
21649967aa | ||
|
|
3f40f60c1c | ||
|
|
176c275a06 | ||
|
|
ae2b4b1668 | ||
|
|
a27f8e2937 | ||
|
|
818b1c84b0 | ||
|
|
75eceaa5e1 | ||
|
|
c6b4cc8e01 | ||
|
|
819302b51f | ||
|
|
c53d7fcc32 | ||
|
|
3de2e65530 | ||
|
|
c5319b6bea | ||
|
|
801ddc0886 | ||
|
|
dfd9196aa7 | ||
|
|
c4362297f5 | ||
|
|
96c23af99c | ||
|
|
6a29dacdeb | ||
|
|
9ddad80f2a | ||
|
|
1944fe4029 | ||
|
|
9921736102 | ||
|
|
11aa641dbc | ||
|
|
2f3290592b | ||
|
|
7b3fe73474 | ||
|
|
4237354bb7 | ||
|
|
f7354c9842 | ||
|
|
1636786af8 | ||
|
|
de0a716f41 | ||
|
|
9f108c406b | ||
|
|
5346d13674 | ||
|
|
d054079eeb | ||
|
|
fe6324f84d | ||
|
|
4a70eb5956 | ||
|
|
1a7b86d155 | ||
|
|
f66f8c4d59 | ||
|
|
1a9c3c3632 | ||
|
|
91f4892b0c | ||
|
|
d6d0a98afe | ||
|
|
8efc5af2bc | ||
|
|
104b62f4d8 | ||
|
|
8e9dbe31fa | ||
|
|
cf0afa01c8 | ||
|
|
c5b67ac60b | ||
|
|
110c6fc08f | ||
|
|
f349ecdd3c | ||
|
|
57fb481da9 | ||
|
|
5871e0afe1 | ||
|
|
281879ea98 | ||
|
|
b623380dd0 | ||
|
|
7645094df6 | ||
|
|
733b1885ff | ||
|
|
7adbb1e0af | ||
|
|
6d5967c981 | ||
|
|
b15f2a2bb6 | ||
|
|
3cc0a41e50 | ||
|
|
4abdca9fdd | ||
|
|
99ef9366d6 | ||
|
|
235ee5dff9 | ||
|
|
20f25803d4 | ||
|
|
09380698fa | ||
|
|
1d96961f25 | ||
|
|
b38241c675 | ||
|
|
1dbd3240a3 | ||
|
|
47e6c0891e | ||
|
|
a6e35ed70f | ||
|
|
f570c3fa06 | ||
|
|
50556a295f | ||
|
|
acdca350a6 | ||
|
|
fe55c7ae7a | ||
|
|
8dbc91ee6b | ||
|
|
783f197463 | ||
|
|
4b7c9782b5 | ||
|
|
f109bfbb6a | ||
|
|
baf00f203a | ||
|
|
1309b1f11b | ||
|
|
a146c93285 | ||
|
|
53cccef570 | ||
|
|
b26c2bbc98 | ||
|
|
4b5246394b | ||
|
|
18b93613cd | ||
|
|
1f78c236a9 | ||
|
|
09035643c3 | ||
|
|
97be3bcd05 | ||
|
|
a6de430cf3 |
9
.env
@@ -1,2 +1,9 @@
|
||||
# These API keys are intentionally public. Please do not report them - thank you for your concern.
|
||||
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
|
||||
REACT_APP_FORTMATIC_KEY="pk_live_357F77728B8EB880"
|
||||
REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
|
||||
REACT_APP_AWS_API_REGION="us-east-2"
|
||||
REACT_APP_AWS_API_ACCESS_KEY="AKIAYJJWW6AQ47ODATHN"
|
||||
REACT_APP_AWS_API_ACCESS_SECRET="V9PoU0FhBP3cX760rPs9jMG/MIuDNLX6hYvVcaYO"
|
||||
REACT_APP_AWS_X_API_KEY="z9dReS5UtHu7iTrUsTuWRozLthi3AxOZlvobrIdr14"
|
||||
REACT_APP_AWS_API_ENDPOINT="https://beta.api.uniswap.org/v1/graphql"
|
||||
REACT_APP_TEMP_API_URL="https://temp.api.uniswap.org/v1"
|
||||
@@ -1,5 +1,7 @@
|
||||
REACT_APP_AMPLITUDE_KEY="1c694b28cd089acc2c386d518f93a775"
|
||||
REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
|
||||
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
|
||||
REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID="G-KDP9B6W4H8"
|
||||
REACT_APP_FIREBASE_KEY="AIzaSyBcZWwTcTJHj_R6ipZcrJkXdq05PuX0Rs0"
|
||||
REACT_APP_AWS_API_ENDPOINT="https://api.uniswap.org/v1/graphql"
|
||||
THE_GRAPH_SCHEMA_ENDPOINT="https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3"
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"src/abis/types",
|
||||
"src/locales/**/*.js",
|
||||
"src/locales/**/en-US.po",
|
||||
"src/state/data/generated.ts",
|
||||
"node_modules",
|
||||
"coverage",
|
||||
"build",
|
||||
@@ -53,6 +52,7 @@
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/jsx-curly-brace-presence": ["error", {"props": "never", "children": "never" }],
|
||||
"object-shorthand": ["error", "always"],
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
@@ -82,6 +82,18 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
{
|
||||
"name": "@ethersproject/providers",
|
||||
"message": "Please only use Providers instantiated in constants/providers to improve traceability.",
|
||||
"allowTypeImports": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
||||
@uniswap/web
|
||||
3
.github/dependabot.yml
vendored
@@ -6,5 +6,6 @@ updates:
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
allow:
|
||||
- dependency-name: '@uniswap/token-lists'
|
||||
- dependency-name: '@uniswap/default-token-list'
|
||||
- dependency-name: '@uniswap/token-lists'
|
||||
- dependency-name: '@uniswap/widgets'
|
||||
|
||||
5
.github/workflows/release.yaml
vendored
@@ -88,10 +88,9 @@ jobs:
|
||||
- name: Update DNS with new IPFS hash
|
||||
env:
|
||||
CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }}
|
||||
RECORD_DOMAIN: 'uniswap.org'
|
||||
RECORD_NAME: '_dnslink.app'
|
||||
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
|
||||
uses: textileio/cloudflare-update-dnslink@0fe7b7a1ffc865db3a4da9773f0f987447ad5848
|
||||
CLOUDFLARE_GATEWAY_ID: ${{ secrets.CLOUDFLARE_GATEWAY_ID }}
|
||||
uses: Uniswap/cloudflare-update-web3-gateway@b3205288b1c6d0acb63fa3bd8fb686c72a9e3f3e
|
||||
with:
|
||||
cid: ${{ steps.pinata.outputs.hash }}
|
||||
|
||||
|
||||
4
.github/workflows/test.yml
vendored
@@ -5,8 +5,6 @@ on:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
# manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -23,7 +21,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- run: npx yarn-deduplicate --strategy=highest --list --fail
|
||||
- run: yarn yarn-deduplicate --strategy=highest --list --fail
|
||||
|
||||
unit-tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
7
.gitignore
vendored
@@ -1,12 +1,16 @@
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
|
||||
# generated contract types
|
||||
/src/types/v3
|
||||
/src/abis/types
|
||||
/src/locales/**/*.js
|
||||
/src/locales/**/en-US.po
|
||||
/src/locales/**/pseudo.po
|
||||
/src/state/data/generated.ts
|
||||
|
||||
# generated graphql types
|
||||
__generated__/
|
||||
schema.graphql
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
@@ -39,3 +43,4 @@ package-lock.json
|
||||
cypress/videos
|
||||
cypress/screenshots
|
||||
|
||||
.vercel
|
||||
|
||||
@@ -1 +1 @@
|
||||
/src/state/data/generated.ts
|
||||
/src/schema/schema.graphql
|
||||
@@ -30,7 +30,7 @@ or visit [app.uniswap.org](https://app.uniswap.org).
|
||||
|
||||
Check out `useUnsupportedTokenList()` in [src/state/lists/hooks.ts](./src/state/lists/hooks.ts) for blocking tokens in your instance of the interface.
|
||||
|
||||
You can block an entire list of tokens by passing in a tokenlist like [here](./src/constants/lists.ts) or you can block specific tokens by adding them to [unsupported.tokenlist.json](./src/constants/tokenLists/unsupported.tokenlist.json).
|
||||
You can block an entire list of tokens by passing in a tokenlist like [here](./src/constants/lists.ts)
|
||||
|
||||
## Contributions
|
||||
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
overrideExisting: true
|
||||
schema: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3'
|
||||
documents: 'src/**/!(*.d).{ts,tsx}'
|
||||
generates:
|
||||
./src/state/data/generated.ts:
|
||||
./src/graphql/thegraph/schema/schema.graphql:
|
||||
plugins:
|
||||
- typescript
|
||||
- typescript-operations
|
||||
- typescript-rtk-query:
|
||||
importBaseApiFrom: './slice'
|
||||
exportHooks: true
|
||||
- schema-ast
|
||||
|
||||
37
craco.config.cjs
Normal file
@@ -0,0 +1,37 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const { VanillaExtractPlugin } = require('@vanilla-extract/webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const { DefinePlugin } = require('webpack')
|
||||
|
||||
const commitHash = require('child_process').execSync('git rev-parse HEAD')
|
||||
|
||||
module.exports = {
|
||||
babel: {
|
||||
plugins: ['@vanilla-extract/babel-plugin'],
|
||||
},
|
||||
webpack: {
|
||||
plugins: [
|
||||
new VanillaExtractPlugin(),
|
||||
new DefinePlugin({
|
||||
'process.env.REACT_APP_GIT_COMMIT_HASH': JSON.stringify(commitHash.toString()),
|
||||
}),
|
||||
],
|
||||
configure: (webpackConfig) => {
|
||||
const instanceOfMiniCssExtractPlugin = webpackConfig.plugins.find(
|
||||
(plugin) => plugin instanceof MiniCssExtractPlugin
|
||||
)
|
||||
if (instanceOfMiniCssExtractPlugin !== undefined) instanceOfMiniCssExtractPlugin.options.ignoreOrder = true
|
||||
|
||||
// We're currently on Webpack 4.x that doesn't support the `exports` field in package.json.
|
||||
// See https://github.com/webpack/webpack/issues/9509.
|
||||
//
|
||||
// In case you need to add more modules, make sure to remap them to the correct path.
|
||||
//
|
||||
// Map @uniswap/conedison to its dist folder.
|
||||
// This is required because conedison uses * to redirect all imports to its dist.
|
||||
webpackConfig.resolve.alias['@uniswap/conedison'] = '@uniswap/conedison/dist'
|
||||
|
||||
return webpackConfig
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -21,24 +21,24 @@ describe('Add Liquidity', () => {
|
||||
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('not.contain.text', 'ETH')
|
||||
})
|
||||
|
||||
it('token not in storage is loaded', () => {
|
||||
it.skip('token not in storage is loaded', () => {
|
||||
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL')
|
||||
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('single token can be selected', () => {
|
||||
it.skip('single token can be selected', () => {
|
||||
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL')
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('loads fee tier distribution', () => {
|
||||
it.skip('loads fee tier distribution', () => {
|
||||
cy.fixture('feeTierDistribution.json').then((feeTierDistribution) => {
|
||||
cy.intercept('POST', '/subgraphs/name/uniswap/uniswap-v3', (req: CyHttpMessages.IncomingHttpRequest) => {
|
||||
if (hasQuery(req, 'feeTierDistribution')) {
|
||||
req.alias = 'feeTierDistributionQuery'
|
||||
if (hasQuery(req, 'FeeTierDistributionQuery')) {
|
||||
req.alias = 'FeeTierDistributionQuery'
|
||||
|
||||
req.reply({
|
||||
body: {
|
||||
@@ -55,7 +55,7 @@ describe('Add Liquidity', () => {
|
||||
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab')
|
||||
|
||||
cy.wait('@feeTierDistributionQuery')
|
||||
cy.wait('@FeeTierDistributionQuery')
|
||||
|
||||
cy.get('#add-liquidity-selected-fee .selected-fee-label').should('contain.text', '0.3% fee tier')
|
||||
cy.get('#add-liquidity-selected-fee .selected-fee-percentage').should('contain.text', '40%')
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
describe('Link', () => {
|
||||
it('should update route', () => {
|
||||
cy.visit('/')
|
||||
cy.get('[data-cy="pool-nav-link"]').click()
|
||||
cy.contains('Pool').click()
|
||||
cy.get('[data-cy="join-pool-button"]').should('exist')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
describe('Lists', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/swap')
|
||||
})
|
||||
|
||||
// @TODO check if default lists are active when we have them
|
||||
it('change list', () => {
|
||||
cy.get('#swap-currency-output .open-currency-select-button').click()
|
||||
cy.get('.list-token-manage-button').click()
|
||||
})
|
||||
})
|
||||
@@ -23,7 +23,7 @@ describe('Remove Liquidity', () => {
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'WETH')
|
||||
})
|
||||
|
||||
it('token not in storage is loaded', () => {
|
||||
it.skip('token not in storage is loaded', () => {
|
||||
cy.visit('/remove/v2/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'SKL')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
|
||||
|
||||
@@ -7,7 +7,7 @@ describe('Swap', () => {
|
||||
cy.get('#swap-currency-input .token-amount-input').should('have.value', '')
|
||||
cy.get('#swap-currency-input .token-symbol-container').should('contain.text', 'ETH')
|
||||
cy.get('#swap-currency-output .token-amount-input').should('not.have.value')
|
||||
cy.get('#swap-currency-output .token-symbol-container').should('contain.text', 'Select a token')
|
||||
cy.get('#swap-currency-output .token-symbol-container').should('contain.text', 'Select token')
|
||||
})
|
||||
|
||||
it('can enter an amount into input', () => {
|
||||
|
||||
50
cypress/e2e/wallet-dropdown.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
describe('Wallet Dropdown', () => {
|
||||
before(() => {
|
||||
cy.visit('/', { featureFlags: [FeatureFlag.navBar, FeatureFlag.tokenSafety] })
|
||||
})
|
||||
|
||||
it('should change the theme', () => {
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).contains('Light theme').should('exist')
|
||||
})
|
||||
|
||||
it('should select a language', () => {
|
||||
cy.get(getTestSelector('wallet-select-language')).click()
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Taal')
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
|
||||
cy.get(getTestSelector('wallet-back')).click()
|
||||
})
|
||||
|
||||
it('should be able to view transactions', () => {
|
||||
cy.get(getTestSelector('wallet-transactions')).click()
|
||||
cy.get(getTestSelector('wallet-empty-transaction-text')).should('exist')
|
||||
cy.get(getTestSelector('wallet-back')).click()
|
||||
})
|
||||
|
||||
it('should change the theme when not connected', () => {
|
||||
cy.get(getTestSelector('wallet-disconnect')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).contains('Dark theme').should('exist')
|
||||
})
|
||||
|
||||
it('should select a language when not connected', () => {
|
||||
cy.get(getTestSelector('wallet-select-language')).click()
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Taal')
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
|
||||
cy.get(getTestSelector('wallet-back')).click()
|
||||
})
|
||||
|
||||
it('should open the wallet connect modal from the drop down when not connected', () => {
|
||||
cy.get(getTestSelector('wallet-connect-wallet')).click()
|
||||
cy.get(getTestSelector('wallet-modal')).should('exist')
|
||||
cy.get(getTestSelector('wallet-modal-close')).click()
|
||||
})
|
||||
})
|
||||
@@ -1,30 +0,0 @@
|
||||
import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/ethereum'
|
||||
|
||||
describe('Wallet', () => {
|
||||
before(() => {
|
||||
cy.visit('/swap')
|
||||
})
|
||||
|
||||
it('displays account details', () => {
|
||||
cy.get('[data-testid=web3-status-connected]').contains(TEST_ADDRESS_NEVER_USE_SHORTENED).click()
|
||||
})
|
||||
|
||||
it('displays account view in wallet modal', () => {
|
||||
cy.get('[data-testid=web3-account-identifier-row]').contains(TEST_ADDRESS_NEVER_USE_SHORTENED)
|
||||
})
|
||||
|
||||
it('changes back to the options grid', () => {
|
||||
cy.contains('Change').click()
|
||||
cy.get('[data-testid=option-grid]').should('exist')
|
||||
})
|
||||
|
||||
it('selects injected wallet option', () => {
|
||||
cy.contains('Injected').click()
|
||||
cy.get('[data-testid=web3-account-identifier-row]').contains(TEST_ADDRESS_NEVER_USE_SHORTENED)
|
||||
})
|
||||
|
||||
it('shows connect buttons after disconnect', () => {
|
||||
cy.contains('Disconnect').click()
|
||||
cy.get('[data-testid=option-grid]').should('exist')
|
||||
})
|
||||
})
|
||||
@@ -9,6 +9,8 @@
|
||||
import { injected } from './ethereum'
|
||||
import assert = require('assert')
|
||||
|
||||
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
@@ -17,6 +19,7 @@ declare global {
|
||||
}
|
||||
interface VisitOptions {
|
||||
serviceWorker?: true
|
||||
featureFlags?: Array<FeatureFlag>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,10 +34,23 @@ Cypress.Commands.overwrite(
|
||||
cy.intercept('/service-worker.js', options?.serviceWorker ? undefined : { statusCode: 404 }).then(() => {
|
||||
original({
|
||||
...options,
|
||||
url: (url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url) + '?chain=rinkeby',
|
||||
url: (url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url) + '?chain=goerli',
|
||||
onBeforeLoad(win) {
|
||||
options?.onBeforeLoad?.(win)
|
||||
win.localStorage.clear()
|
||||
win.localStorage.setItem('redux_localstorage_simple_user', '{"selectedWallet":"INJECTED"}')
|
||||
|
||||
if (options?.featureFlags) {
|
||||
const featureFlags = options.featureFlags.reduce(
|
||||
(flags, flag) => ({
|
||||
...flags,
|
||||
[flag]: 'enabled',
|
||||
}),
|
||||
{}
|
||||
)
|
||||
win.localStorage.setItem('featureFlags', JSON.stringify(featureFlags))
|
||||
}
|
||||
|
||||
win.ethereum = injected
|
||||
},
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import { JsonRpcProvider } from '@ethersproject/providers'
|
||||
import { Wallet } from '@ethersproject/wallet'
|
||||
|
||||
@@ -18,10 +19,10 @@ export const TEST_ADDRESS_NEVER_USE_SHORTENED = `${TEST_ADDRESS_NEVER_USE.substr
|
||||
6
|
||||
)}...${TEST_ADDRESS_NEVER_USE.substr(-4, 4)}`
|
||||
|
||||
const provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
|
||||
const provider = new JsonRpcProvider('https://goerli.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
|
||||
const signer = new Wallet(TEST_PRIVATE_KEY, provider)
|
||||
export const injected = new (class extends Eip1193Bridge {
|
||||
chainId = 4
|
||||
chainId = /* GOERLI= */ 5
|
||||
|
||||
async sendAsync(...args: any[]) {
|
||||
console.debug('sendAsync called', ...args)
|
||||
|
||||
1
cypress/utils/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const getTestSelector = (selectorId: string) => `[data-testid=${selectorId}]`
|
||||
22
fetch-schema.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/* eslint-disable */
|
||||
require('dotenv').config({ path: '.env.production' })
|
||||
const { exec } = require('child_process')
|
||||
const dataConfig = require('./relay.config')
|
||||
const thegraphConfig = require('./relay_thegraph.config')
|
||||
/* eslint-enable */
|
||||
|
||||
function fetchSchema(url, outputFile) {
|
||||
exec(
|
||||
`get-graphql-schema --h Origin=https://app.uniswap.org ${url} | tee ${outputFile}.temp`,
|
||||
(error, stdout, stderr) => {
|
||||
if (error || stderr) {
|
||||
console.log(`Failed to fetch schema from ${url}`)
|
||||
} else if (stdout) {
|
||||
exec(`mv ${outputFile}.temp ${outputFile}`)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fetchSchema(process.env.THE_GRAPH_SCHEMA_ENDPOINT, thegraphConfig.schema)
|
||||
fetchSchema(process.env.REACT_APP_AWS_API_ENDPOINT, dataConfig.schema)
|
||||
106
package.json
@@ -8,19 +8,24 @@
|
||||
"contracts:compile:abi": "typechain --target ethers-v5 --out-dir src/abis/types \"./src/abis/**/*.json\"",
|
||||
"contracts:compile:v3": "typechain --target ethers-v5 --out-dir src/types/v3 \"./node_modules/@uniswap/**/artifacts/contracts/**/*[!dbg].json\"",
|
||||
"contracts:compile": "yarn contracts:compile:abi && yarn contracts:compile:v3",
|
||||
"graphql:generate": "graphql-codegen --config codegen.yml",
|
||||
"relay": "relay-compiler relay.config.js",
|
||||
"relay-thegraph": "relay-compiler relay_thegraph.config.js",
|
||||
"graphql:fetch": "node fetch-schema.js",
|
||||
"graphql:generate": "yarn relay && yarn relay-thegraph",
|
||||
"prei18n:extract": "node prei18n-extract.js",
|
||||
"i18n:extract": "lingui extract --locale en-US",
|
||||
"i18n:compile": "yarn i18n:extract && lingui compile",
|
||||
"i18n:pseudo": "lingui extract --locale pseudo && lingui compile",
|
||||
"prepare": "yarn contracts:compile && yarn graphql:generate && yarn i18n:compile",
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"prepare": "yarn contracts:compile && yarn graphql:fetch && yarn graphql:generate && yarn i18n:compile",
|
||||
"start": "craco start",
|
||||
"build": "craco build",
|
||||
"serve": "serve build -l 3000",
|
||||
"deduplicate": "yarn-deduplicate --strategy=highest",
|
||||
"lint": "yarn eslint .",
|
||||
"test": "react-scripts test --coverage",
|
||||
"test": "craco test --coverage",
|
||||
"cypress:open": "cypress open --browser chrome --e2e",
|
||||
"cypress:run": "cypress run --browser chrome --e2e"
|
||||
"cypress:run": "cypress run --browser chrome --e2e",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"jest": {
|
||||
"collectCoverageFrom": [
|
||||
@@ -55,11 +60,8 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@craco/craco": "6.4.3",
|
||||
"@ethersproject/experimental": "^5.4.0",
|
||||
"@graphql-codegen/cli": "1.21.5",
|
||||
"@graphql-codegen/typescript": "1.22.3",
|
||||
"@graphql-codegen/typescript-operations": "^1.18.2",
|
||||
"@graphql-codegen/typescript-rtk-query": "^1.1.1",
|
||||
"@lingui/cli": "^3.9.0",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.1",
|
||||
@@ -79,55 +81,69 @@
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-redux": "^7.1.24",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-table": "^7.7.12",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.0",
|
||||
"@types/react-window": "^1.8.2",
|
||||
"@types/rebass": "^4.0.7",
|
||||
"@types/styled-components": "^5.1.25",
|
||||
"@types/testing-library__cypress": "^5.0.5",
|
||||
"@types/ua-parser-js": "^0.7.35",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/wcag-contrast": "^3.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4",
|
||||
"@typescript-eslint/parser": "^4",
|
||||
"@vanilla-extract/babel-plugin": "^1.1.7",
|
||||
"@vanilla-extract/webpack-plugin": "^2.1.11",
|
||||
"babel-plugin-relay": "^14.1.0",
|
||||
"cypress": "^10.3.1",
|
||||
"env-cmd": "^10.1.0",
|
||||
"eslint": "^7.11.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-better-styled-components": "^1.1.2",
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint-plugin-react": "^7.19.0",
|
||||
"eslint-plugin-react-hooks": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.21.5",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"jest-styled-components": "^7.0.8",
|
||||
"ms.macro": "^2.0.0",
|
||||
"patch-package": "^6.4.7",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^2.7.1",
|
||||
"react-scripts": "^4.0.3",
|
||||
"relay-compiler": "^14.1.0",
|
||||
"serve": "^11.3.2",
|
||||
"typechain": "^5.0.0",
|
||||
"typescript": "^4.4.3"
|
||||
"typescript": "^4.4.3",
|
||||
"yarn-deduplicate": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@amplitude/analytics-browser": "^0.5.1",
|
||||
"@amplitude/experiment-js-client": "^1.5.4",
|
||||
"@coinbase/wallet-sdk": "^3.3.0",
|
||||
"@fontsource/ibm-plex-mono": "^4.5.1",
|
||||
"@fontsource/inter": "^4.5.1",
|
||||
"@lingui/core": "^3.14.0",
|
||||
"@lingui/macro": "^3.14.0",
|
||||
"@lingui/react": "^3.14.0",
|
||||
"@looksrare/sdk": "^0.10.2",
|
||||
"@metamask/jazzicon": "^2.0.0",
|
||||
"@opensea/seaport-js": "^1.0.2",
|
||||
"@popperjs/core": "^2.4.4",
|
||||
"@reach/dialog": "^0.10.3",
|
||||
"@reach/portal": "^0.10.3",
|
||||
"@react-hook/window-scroll": "^1.3.0",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"@types/react-relay": "^13.0.2",
|
||||
"@types/react-window-infinite-loader": "^1.0.6",
|
||||
"@uniswap/analytics": "1.1.1",
|
||||
"@uniswap/analytics-events": "1.1.0",
|
||||
"@uniswap/conedison": "^1.1.0",
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
"@uniswap/merkle-distributor": "1.0.1",
|
||||
"@uniswap/redux-multicall": "^1.1.5",
|
||||
"@uniswap/router-sdk": "^1.0.6",
|
||||
"@uniswap/redux-multicall": "^1.1.8",
|
||||
"@uniswap/router-sdk": "^1.3.0",
|
||||
"@uniswap/sdk-core": "^3.0.1",
|
||||
"@uniswap/smart-order-router": "^2.8.2",
|
||||
"@uniswap/smart-order-router": "^2.10.0",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.30",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
@@ -135,34 +151,40 @@
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@uniswap/v3-sdk": "^3.9.0",
|
||||
"@uniswap/widgets": "^2.19.2",
|
||||
"@vanilla-extract/css": "^1.7.2",
|
||||
"@vanilla-extract/css-utils": "^0.1.2",
|
||||
"@vanilla-extract/dynamic": "^2.0.2",
|
||||
"@vanilla-extract/sprinkles": "^1.4.1",
|
||||
"@visx/axis": "^2.12.2",
|
||||
"@visx/event": "^2.6.0",
|
||||
"@visx/glyph": "^2.10.0",
|
||||
"@visx/group": "^2.10.0",
|
||||
"@visx/react-spring": "^2.12.2",
|
||||
"@visx/responsive": "^2.10.0",
|
||||
"@visx/shape": "^2.11.1",
|
||||
"@walletconnect/ethereum-provider": "1.7.1",
|
||||
"@web3-react/coinbase-wallet": "^8.0.34-beta.0",
|
||||
"@web3-react/core": "^8.0.35-beta.0",
|
||||
"@web3-react/eip1193": "^8.0.26-beta.0",
|
||||
"@web3-react/empty": "^8.0.20-beta.0",
|
||||
"@web3-react/gnosis-safe": "^8.0.6-beta.0",
|
||||
"@web3-react/metamask": "^8.0.28-beta.0",
|
||||
"@web3-react/network": "^8.0.27-beta.0",
|
||||
"@web3-react/types": "^8.0.20-beta.0",
|
||||
"@web3-react/url": "^8.0.25-beta.0",
|
||||
"@web3-react/walletconnect": "^8.0.35-beta.0",
|
||||
"ajv": "^6.12.3",
|
||||
"@walletconnect/ethereum-provider": "^1.8.0",
|
||||
"@web3-react/coinbase-wallet": "8.0.34-beta.0",
|
||||
"@web3-react/core": "8.0.35-beta.0",
|
||||
"@web3-react/eip1193": "8.0.26-beta.0",
|
||||
"@web3-react/empty": "8.0.20-beta.0",
|
||||
"@web3-react/gnosis-safe": "8.0.7-beta.0",
|
||||
"@web3-react/metamask": "8.0.28-beta.0",
|
||||
"@web3-react/network": "8.0.27-beta.0",
|
||||
"@web3-react/types": "8.0.20-beta.0",
|
||||
"@web3-react/url": "8.0.25-beta.0",
|
||||
"@web3-react/walletconnect": "8.0.36-beta.0",
|
||||
"array.prototype.flat": "^1.2.4",
|
||||
"array.prototype.flatmap": "^1.2.4",
|
||||
"cids": "^1.0.0",
|
||||
"clsx": "^1.1.1",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"d3": "^7.6.1",
|
||||
"d3-curve-circlecorners": "^0.1.6",
|
||||
"ethers": "^5.1.4",
|
||||
"firebase": "^9.1.3",
|
||||
"fortmatic": "^2.4.0",
|
||||
"graphql": "^15.5.0",
|
||||
"focus-visible": "^5.2.0",
|
||||
"get-graphql-schema": "^2.1.2",
|
||||
"graphql": "^16.5.0",
|
||||
"graphql-request": "^3.4.0",
|
||||
"immer": "^9.0.6",
|
||||
"inter-ui": "^3.13.1",
|
||||
@@ -177,20 +199,26 @@
|
||||
"polyfill-object.fromentries": "^1.0.1",
|
||||
"popper-max-size-modifier": "^0.2.0",
|
||||
"qs": "^6.9.4",
|
||||
"rc-slider": "^10.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-confetti": "^6.0.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-feather": "^2.0.8",
|
||||
"react-ga4": "^1.4.1",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-is": "^17.0.2",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-popper": "^2.2.3",
|
||||
"react-query": "^3.39.1",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-relay": "^14.1.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-spring": "^9.5.5",
|
||||
"react-table": "^7.8.0",
|
||||
"react-use-gesture": "^6.0.14",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
"react-window": "^1.8.5",
|
||||
"react-window-infinite-loader": "^1.0.8",
|
||||
"rebass": "^4.0.7",
|
||||
"redux": "^4.1.2",
|
||||
"redux-localstorage-simple": "^2.3.1",
|
||||
@@ -200,11 +228,19 @@
|
||||
"ua-parser-js": "^0.7.28",
|
||||
"use-count-up": "^2.2.5",
|
||||
"use-resize-observer": "^9.0.2",
|
||||
"uuid": "^8.3.2",
|
||||
"video-extensions": "^1.2.0",
|
||||
"wcag-contrast": "^3.0.0",
|
||||
"web-vitals": "^2.1.0",
|
||||
"workbox-core": "^6.1.0",
|
||||
"workbox-navigation-preload": "^6.1.0",
|
||||
"workbox-precaching": "^6.1.0",
|
||||
"workbox-routing": "^6.1.0"
|
||||
"workbox-routing": "^6.1.0",
|
||||
"zustand": "^4.0.0-rc.1"
|
||||
},
|
||||
"engines": {
|
||||
"npm": "please-use-yarn",
|
||||
"node": "14",
|
||||
"yarn": ">=1.22"
|
||||
}
|
||||
}
|
||||
|
||||
22
patches/@vanilla-extract+css+1.7.2.patch
Normal file
@@ -0,0 +1,22 @@
|
||||
diff --git a/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.cjs.dev.js b/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.cjs.dev.js
|
||||
index 6e40061..10283a2 100644
|
||||
--- a/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.cjs.dev.js
|
||||
+++ b/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.cjs.dev.js
|
||||
@@ -177,11 +177,13 @@ function generateIdentifier(debugId) {
|
||||
var fileScopeHash = hash__default["default"](packageName ? "".concat(packageName).concat(filePath) : filePath);
|
||||
var identifier = "".concat(fileScopeHash).concat(refCount);
|
||||
|
||||
- if (adapter_dist_vanillaExtractCssAdapter.getIdentOption() === 'debug') {
|
||||
- var devPrefix = getDevPrefix(debugId);
|
||||
+ if (process.env.VANILLA_EXTRACT_DEV_PREFIX) {
|
||||
+ if (adapter_dist_vanillaExtractCssAdapter.getIdentOption() === 'debug') {
|
||||
+ var devPrefix = getDevPrefix(debugId);
|
||||
|
||||
- if (devPrefix) {
|
||||
- identifier = "".concat(devPrefix, "__").concat(identifier);
|
||||
+ if (devPrefix) {
|
||||
+ identifier = "".concat(devPrefix, "__").concat(identifier);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const exec = require('child_process').exec
|
||||
const { exec } = require('child_process')
|
||||
const isWindows = process.platform === 'win32' || /^(msys|cygwin)$/.test(process.env.OSTYPE)
|
||||
|
||||
if (isWindows) {
|
||||
|
||||
@@ -15,8 +15,11 @@
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="%PUBLIC_URL%/images/512x512_App_Icon.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#ff007a" />
|
||||
<meta name="fortmatic-site-verification" content="j93LgcVZk79qcgyo" />
|
||||
<meta name="theme-color" content="#FC72FF" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
|
||||
/>
|
||||
|
||||
<!--
|
||||
manifest.json provides metadata used when the app is installed as a PWA.
|
||||
@@ -71,10 +74,10 @@
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
font-feature-settings: 'ss01' on, 'ss02' on, 'cv01' on, 'cv03' on;
|
||||
}
|
||||
|
||||
#background-radial-gradient {
|
||||
background: linear-gradient(180deg, #202738 0%, #070816 100%);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@@ -82,7 +85,6 @@
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
background: radial-gradient(50% 50% at 50% 50%, #fc077d10 0%, rgba(255, 255, 255, 0) 100%);
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
@@ -96,6 +98,7 @@
|
||||
background-color: #212429;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background-color: #f7f8fa;
|
||||
@@ -103,26 +106,15 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
<!-- The root is the container of the app -->
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </div>
|
||||
</div>
|
||||
|
||||
<div id="background-radial-gradient"></div>
|
||||
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
"background_color": "#fff",
|
||||
"display": "standalone",
|
||||
"homepage_url": "https://app.uniswap.org",
|
||||
"providedBy": { "name": "Uniswap", "url": "https://uniswap.org" },
|
||||
"providedBy": {
|
||||
"name": "Uniswap",
|
||||
"url": "https://uniswap.org"
|
||||
},
|
||||
"icons": [
|
||||
{
|
||||
"src": "./images/192x192_App_Icon.png",
|
||||
@@ -23,5 +26,5 @@
|
||||
"iconPath": "./images/256x256_App_Icon_Pink.svg",
|
||||
"short_name": "Uniswap",
|
||||
"start_url": ".",
|
||||
"theme_color": "#ff007a"
|
||||
}
|
||||
"theme_color": "#FC72FF"
|
||||
}
|
||||
3
public/nft/svgs/marketplaces/looksrare-grey.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.64864 2L1 7.65256L10.5 17.1487L20 7.65256L14.3514 2H6.64864ZM6.13513 5.59458C8.5352 3.18398 12.4648 3.18396 14.8649 5.59456L16.9189 7.64866L14.8649 9.70272C12.4648 12.1133 8.5352 12.1133 6.13513 9.70274L4.08109 7.64866L6.13513 5.59458ZM7.54702 7.64848C7.54702 9.27987 8.86966 10.6012 10.4997 10.6012C12.1298 10.6012 13.4524 9.27987 13.4524 7.64848C13.4524 6.01708 12.1298 4.69576 10.4997 4.69576C8.86966 4.69576 7.54702 6.01708 7.54702 7.64848ZM10.4997 8.93225C9.791 8.93225 9.21593 8.35778 9.21593 7.64848C9.21593 6.93917 9.791 6.3647 10.4997 6.3647C11.2084 6.3647 11.7835 6.93917 11.7835 7.64848C11.7835 8.35778 11.2084 8.93225 10.4997 8.93225Z" fill="#5D6785"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 820 B |
3
public/nft/svgs/marketplaces/opensea-grey.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 1C5.0302 1 1 5.0302 1 10C1 14.9698 5.0302 19 10 19C14.9698 19 19 14.9698 19 10C19 5.0302 14.9716 1 10 1ZM5.4406 10.3024L5.4784 10.2412L7.8202 6.5782C7.8544 6.526 7.9354 6.5314 7.9606 6.589C8.3512 7.4656 8.6896 8.5564 8.5312 9.235C8.4646 9.514 8.2792 9.892 8.0704 10.2412C8.0434 10.2916 8.0146 10.342 7.9822 10.3906C7.9678 10.4122 7.9426 10.4248 7.9156 10.4248H5.509C5.4442 10.4248 5.4064 10.3546 5.4406 10.3024ZM15.8752 11.5624C15.8752 11.5966 15.8554 11.6254 15.8266 11.638C15.6448 11.7154 15.0238 12.0016 14.7664 12.3598C14.1076 13.276 13.6054 14.5864 12.4804 14.5864H7.7896C6.1264 14.5864 4.78 13.2346 4.78 11.566V11.512C4.78 11.4688 4.816 11.4328 4.861 11.4328H7.4746C7.5268 11.4328 7.5646 11.4796 7.561 11.5318C7.5412 11.701 7.5736 11.8756 7.6546 12.034C7.8094 12.349 8.1316 12.5452 8.479 12.5452H9.7732V11.5354H8.4934C8.4286 11.5354 8.389 11.4598 8.4268 11.4058C8.4412 11.3842 8.4556 11.3626 8.4736 11.3374C8.5942 11.1646 8.767 10.8982 8.9398 10.594C9.0568 10.3888 9.1702 10.1692 9.262 9.9496C9.28 9.91 9.2944 9.8686 9.3106 9.829C9.3358 9.7588 9.361 9.6922 9.379 9.6274C9.397 9.5716 9.4132 9.514 9.4276 9.46C9.4708 9.2728 9.4888 9.0748 9.4888 8.8696C9.4888 8.7886 9.4852 8.704 9.478 8.6248C9.4744 8.5366 9.4636 8.4484 9.4528 8.3602C9.4456 8.2828 9.4312 8.2054 9.4168 8.1262C9.397 8.0092 9.3718 7.8922 9.343 7.7752L9.3322 7.7302C9.3106 7.6492 9.2908 7.5736 9.2656 7.4926C9.1918 7.2406 9.109 6.994 9.019 6.7636C8.9866 6.6718 8.9506 6.5836 8.9128 6.4972C8.8588 6.364 8.803 6.2434 8.7526 6.13C8.7256 6.0778 8.704 6.031 8.6824 5.9824C8.6572 5.9284 8.632 5.8744 8.605 5.8222C8.587 5.7826 8.5654 5.7448 8.551 5.7088L8.3926 5.4172C8.371 5.3776 8.407 5.329 8.4502 5.3416L9.4402 5.6098H9.4438C9.4456 5.6098 9.4456 5.6098 9.4474 5.6098L9.577 5.6476L9.721 5.6872L9.7732 5.7016V5.1148C9.7732 4.8304 10 4.6 10.2826 4.6C10.423 4.6 10.5508 4.6576 10.6408 4.7512C10.7326 4.8448 10.7902 4.9726 10.7902 5.1148V5.9878L10.8964 6.0166C10.9036 6.0202 10.9126 6.0238 10.9198 6.0292C10.945 6.0472 10.9828 6.076 11.0296 6.112C11.0674 6.1408 11.107 6.1768 11.1538 6.2146C11.2492 6.292 11.3644 6.391 11.4886 6.5044C11.521 6.5332 11.5534 6.562 11.584 6.5926C11.7442 6.742 11.9242 6.9166 12.097 7.111C12.1456 7.1668 12.1924 7.2208 12.241 7.2802C12.2878 7.3396 12.34 7.3972 12.3832 7.4548C12.4426 7.5322 12.5038 7.6132 12.5596 7.6978C12.5848 7.7374 12.6154 7.7788 12.6388 7.8184C12.7108 7.9246 12.772 8.0344 12.8314 8.1442C12.8566 8.1946 12.8818 8.2504 12.9034 8.3044C12.97 8.452 13.0222 8.6014 13.0546 8.7526C13.0654 8.785 13.0726 8.8192 13.0762 8.8516V8.8588C13.087 8.902 13.0906 8.9488 13.0942 8.9974C13.1086 9.1504 13.1014 9.3052 13.069 9.46C13.0546 9.5248 13.0366 9.586 13.015 9.6526C12.9916 9.7156 12.97 9.7804 12.9412 9.8434C12.8854 9.9712 12.8206 10.1008 12.7432 10.2196C12.718 10.2646 12.6874 10.3114 12.6586 10.3564C12.6262 10.4032 12.592 10.4482 12.5632 10.4914C12.5218 10.5472 12.4786 10.6048 12.4336 10.657C12.394 10.711 12.3544 10.765 12.3094 10.8136C12.2482 10.8874 12.1888 10.9558 12.1258 11.0224C12.0898 11.0656 12.0502 11.1106 12.0088 11.1502C11.9692 11.1952 11.9278 11.2348 11.8918 11.2708C11.8288 11.3338 11.7784 11.3806 11.7352 11.422L11.6326 11.5138C11.6182 11.5282 11.5984 11.5354 11.5786 11.5354H10.7902V12.5452H11.782C12.0034 12.5452 12.214 12.4678 12.385 12.322C12.4426 12.2716 12.6964 12.052 12.997 11.7208C13.0078 11.7082 13.0204 11.701 13.0348 11.6974L15.7726 10.9054C15.8248 10.891 15.8752 10.9288 15.8752 10.9828V11.5624Z" fill="#5D6785"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
6
public/nft/svgs/marketplaces/sudoswap.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 14.4C0 6.4471 6.4471 0 14.4 0H33.6C41.5529 0 48 6.4471 48 14.4V33.6C48 41.5529 41.5529 48 33.6 48H14.4C6.4471 48 0 41.5529 0 33.6V14.4Z" fill="#B9B9FF"/>
|
||||
<path d="M9.29999 15H12L15.3 24L12 33H9.29999L12.6 24L9.29999 15Z" fill="#121212"/>
|
||||
<path d="M23.5727 33.192C22.1807 33.192 21.0367 32.872 20.1407 32.232C19.2607 31.592 18.8207 30.72 18.8207 29.616H21.2447C21.2447 30.112 21.4607 30.496 21.8927 30.768C22.3407 31.024 22.9167 31.152 23.6207 31.152H24.6287C25.4607 31.152 26.0767 30.992 26.4767 30.672C26.8767 30.336 27.0767 29.896 27.0767 29.352C27.0767 28.808 26.8847 28.384 26.5007 28.08C26.1167 27.776 25.5727 27.56 24.8687 27.432L23.0687 27.168C20.4447 26.752 19.1327 25.48 19.1327 23.352C19.1327 22.136 19.5327 21.208 20.3327 20.568C21.1327 19.928 22.2767 19.608 23.7647 19.608H24.6767C26.0527 19.608 27.1567 19.92 27.9887 20.544C28.8207 21.152 29.2367 21.96 29.2367 22.968H26.8127C26.8127 22.568 26.6127 22.248 26.2127 22.008C25.8287 21.768 25.3007 21.648 24.6287 21.648H23.7167C22.9807 21.648 22.4207 21.8 22.0367 22.104C21.6527 22.392 21.4607 22.808 21.4607 23.352C21.4607 24.28 22.1167 24.848 23.4287 25.056L25.2287 25.344C26.6687 25.568 27.7247 25.992 28.3967 26.616C29.0687 27.224 29.4047 28.104 29.4047 29.256C29.4047 30.488 28.9967 31.456 28.1807 32.16C27.3807 32.848 26.1807 33.192 24.5807 33.192H23.5727Z" fill="#121212"/>
|
||||
<path d="M38.7 15H36L32.7 24L36 33H38.7L35.4 24L38.7 15Z" fill="#121212"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
3
public/nft/svgs/marketplaces/uniswap-magenta.svg
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
5
public/nft/svgs/marketplaces/x2y2-grey.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.146 4.52803C15.767 3.18049 13.8805 2.35 11.8 2.35C7.57502 2.35 4.15 5.77502 4.15 10C4.15 14.225 7.57502 17.65 11.8 17.65C13.8805 17.65 15.767 16.8195 17.146 15.472C15.501 17.617 12.912 19 10 19C5.02944 19 1 14.9706 1 10C1 5.02944 5.02944 1 10 1C12.912 1 15.501 2.38301 17.146 4.52803Z" fill="#5D6785"/>
|
||||
<path d="M6.08317 14.3776C7.18644 15.4556 8.69563 16.12 10.36 16.12C13.74 16.12 16.48 13.38 16.48 10C16.48 6.62002 13.74 3.88 10.36 3.88C8.69563 3.88 7.18644 4.54439 6.08317 5.62243C7.39916 3.90641 9.47037 2.8 11.8 2.8C15.7765 2.8 19 6.02355 19 10C19 13.9764 15.7764 17.2 11.8 17.2C9.47037 17.2 7.39916 16.0936 6.08317 14.3776Z" fill="#5D6785"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.4 10C15.4 12.9823 12.9823 15.4 10 15.4C7.01766 15.4 4.6 12.9823 4.6 10C4.6 7.01766 7.01766 4.6 10 4.6C12.9823 4.6 15.4 7.01766 15.4 10ZM13.6 10C13.6 11.9882 11.9882 13.6 10 13.6C8.01177 13.6 6.4 11.9882 6.4 10C6.4 8.01178 8.01177 6.4 10 6.4C11.9882 6.4 13.6 8.01178 13.6 10Z" fill="#5D6785"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
6
relay.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
src: './src',
|
||||
language: 'typescript',
|
||||
schema: './src/graphql/data/schema.graphql',
|
||||
exclude: ['**/node_modules/**', '**/__mocks__/**', '**/__generated__/**', '**/thegraph/**'],
|
||||
}
|
||||
9
relay_thegraph.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const defaultConfig = require('./relay.config')
|
||||
|
||||
module.exports = {
|
||||
src: defaultConfig.src,
|
||||
language: defaultConfig.language,
|
||||
schema: './src/graphql/thegraph/schema.graphql',
|
||||
exclude: ['**/node_modules/**', '**/__mocks__/**', '**/__generated__/**', '**/data/**'],
|
||||
}
|
||||
@@ -1,15 +1,143 @@
|
||||
[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "uri_",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "operator",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bool",
|
||||
"name": "approved",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"name": "ApprovalForAll",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "operator",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256[]",
|
||||
"name": "ids",
|
||||
"type": "uint256[]"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256[]",
|
||||
"name": "values",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"name": "TransferBatch",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "operator",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "id",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "TransferSingle",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "string",
|
||||
"name": "value",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "id",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "URI",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_owner",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "_id",
|
||||
"name": "id",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
@@ -21,16 +149,165 @@
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "accounts",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "ids",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"name": "balanceOfBatch",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "operator",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "isApprovedForAll",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "ids",
|
||||
"type": "uint256[]"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "amounts",
|
||||
"type": "uint256[]"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "data",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "safeBatchTransferFrom",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "id",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "data",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "safeTransferFrom",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "operator",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "approved",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"name": "setApprovalForAll",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "bytes4",
|
||||
"name": "interfaceId",
|
||||
"type": "bytes4"
|
||||
}
|
||||
],
|
||||
"name": "supportsInterface",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "_id",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
@@ -42,7 +319,6 @@
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,320 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "punkContract",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "approved",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "tokenId",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "operator",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bool",
|
||||
"name": "approved",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"name": "ApprovalForAll",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "previousOwner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnershipTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "Paused",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "address",
|
||||
"name": "user",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "address",
|
||||
"name": "proxy",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "ProxyRegistered",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "tokenId",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "Unpaused",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "tokenId",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "baseURI",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "punkIndex",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "burn",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "tokenId",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getApproved",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "operator",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "isApprovedForAll",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "punkIndex",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "mint",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
@@ -15,10 +330,263 @@
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [],
|
||||
"name": "pause",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "paused",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "user",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "proxyInfo",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "punkContract",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [],
|
||||
"name": "registerProxy",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [],
|
||||
"name": "renounceOwnership",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "tokenId",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "safeTransferFrom",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "tokenId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "_data",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "safeTransferFrom",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "approved",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"name": "setApprovalForAll",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "baseUri",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"name": "setBaseURI",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "bytes4",
|
||||
"name": "interfaceId",
|
||||
"type": "bytes4"
|
||||
}
|
||||
],
|
||||
"name": "supportsInterface",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "tokenByIndex",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "tokenOfOwnerByIndex",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
@@ -34,7 +602,72 @@
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "tokenId",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "transferOwnership",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [],
|
||||
"name": "unpause",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
|
||||
218
src/abis/uniswap-nft-airdrop-claim.json
Normal file
@@ -0,0 +1,218 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "token_",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "merkleRoot_",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "endTime_",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "AlreadyClaimed",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "ClaimWindowFinished",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "EndTimeInPast",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "InvalidProof",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "NoWithdrawDuringClaim",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Claimed",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "previousOwner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnershipTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32[]",
|
||||
"name": "merkleProof",
|
||||
"type": "bytes32[]"
|
||||
}
|
||||
],
|
||||
"name": "claim",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "endTime",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "isClaimed",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "merkleRoot",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "renounceOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "token",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "transferOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "withdraw",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
BIN
src/assets/images/airdopBackground.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
src/assets/images/arbitrumCircle.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
src/assets/images/celoCircle.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/images/polygonCircle.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/images/sizingImage.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src/assets/images/welcomeModal-dark.jpg
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
src/assets/images/welcomeModal-dark@2x.jpg
Normal file
|
After Width: | Height: | Size: 449 KiB |
BIN
src/assets/images/welcomeModal-dark@3x.jpg
Normal file
|
After Width: | Height: | Size: 927 KiB |
BIN
src/assets/images/welcomeModal-light.jpg
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
src/assets/images/welcomeModal-light@2x.jpg
Normal file
|
After Width: | Height: | Size: 364 KiB |
BIN
src/assets/images/welcomeModal-light@3x.jpg
Normal file
|
After Width: | Height: | Size: 718 KiB |
4
src/assets/svg/eye.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="54" height="40" viewBox="0 0 54 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.3335 19.9997C1.3335 19.9997 10.6668 1.33301 27.0002 1.33301C43.3335 1.33301 52.6668 19.9997 52.6668 19.9997C52.6668 19.9997 43.3335 38.6663 27.0002 38.6663C10.6668 38.6663 1.3335 19.9997 1.3335 19.9997Z" stroke="#98A1C0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M27.0002 26.9997C30.8662 26.9997 34.0002 23.8657 34.0002 19.9997C34.0002 16.1337 30.8662 12.9997 27.0002 12.9997C23.1342 12.9997 20.0002 16.1337 20.0002 19.9997C20.0002 23.8657 23.1342 26.9997 27.0002 26.9997Z" stroke="#98A1C0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 705 B |
3
src/assets/svg/share.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="#fff">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4m14-7-5-5-5 5m5-5v12"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 257 B |
186
src/assets/svg/socks.svg
Normal file
@@ -0,0 +1,186 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20">
|
||||
<g filter="url(#a)">
|
||||
<path fill="url(#b)" d="M17.654 4.868c0-.69-.56-1.25-1.25-1.25h-2.932c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.741 1.873c-.898.999-1.115 2.687-.058 3.748 1.013 1.017 2.918.988 3.87 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.158v-7.55Z"/>
|
||||
<path fill="url(#c)" d="M17.654 4.868c0-.69-.56-1.25-1.25-1.25h-2.932c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.741 1.873c-.898.999-1.115 2.687-.058 3.748 1.013 1.017 2.918.988 3.87 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.158v-7.55Z"/>
|
||||
<path fill="url(#d)" d="M17.654 4.868c0-.69-.56-1.25-1.25-1.25h-2.932c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.741 1.873c-.898.999-1.115 2.687-.058 3.748 1.013 1.017 2.918.988 3.87 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.158v-7.55Z"/>
|
||||
<path fill="url(#e)" d="M17.654 4.868c0-.69-.56-1.25-1.25-1.25h-2.932c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.741 1.873c-.898.999-1.115 2.687-.058 3.748 1.013 1.017 2.918.988 3.87 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.158v-7.55Z"/>
|
||||
<path fill="url(#f)" d="M17.654 4.868c0-.69-.56-1.25-1.25-1.25h-2.932c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.741 1.873c-.898.999-1.115 2.687-.058 3.748 1.013 1.017 2.918.988 3.87 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.158v-7.55Z"/>
|
||||
</g>
|
||||
<g filter="url(#g)">
|
||||
<path fill="url(#h)" d="M10.954 2.602c0-.69-.56-1.25-1.25-1.25H6.773c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.742 1.873c-.898 1-1.115 2.687-.058 3.748 1.014 1.018 2.919.988 3.871 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.157V2.602Z"/>
|
||||
<path fill="url(#i)" d="M10.954 2.602c0-.69-.56-1.25-1.25-1.25H6.773c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.742 1.873c-.898 1-1.115 2.687-.058 3.748 1.014 1.018 2.919.988 3.871 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.157V2.602Z"/>
|
||||
<path fill="url(#j)" d="M10.954 2.602c0-.69-.56-1.25-1.25-1.25H6.773c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.742 1.873c-.898 1-1.115 2.687-.058 3.748 1.014 1.018 2.919.988 3.871 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.157V2.602Z"/>
|
||||
<path fill="url(#k)" d="M10.954 2.602c0-.69-.56-1.25-1.25-1.25H6.773c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.742 1.873c-.898 1-1.115 2.687-.058 3.748 1.014 1.018 2.919.988 3.871 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.157V2.602Z"/>
|
||||
<path fill="url(#l)" d="M10.954 2.602c0-.69-.56-1.25-1.25-1.25H6.773c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.742 1.873c-.898 1-1.115 2.687-.058 3.748 1.014 1.018 2.919.988 3.871 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.157V2.602Z"/>
|
||||
</g>
|
||||
<path fill="url(#m)" d="M17.654 5.776h-5.431v.673h5.431v-.673Z"/>
|
||||
<path fill="url(#n)" d="M17.654 5.776h-5.431v.673h5.431v-.673Z"/>
|
||||
<path fill="url(#o)" d="M10.955 3.51H5.523v.674h5.432V3.51Z"/>
|
||||
<path fill="url(#p)" d="M10.955 3.51H5.523v.674h5.432V3.51Z"/>
|
||||
<path fill="url(#q)" d="M17.606 4.523c.031.11.048.225.048.345v.328h-5.431v-.328c0-.12.016-.236.048-.345h5.335Z"/>
|
||||
<path fill="url(#r)" d="M17.606 4.523c.031.11.048.225.048.345v.328h-5.431v-.328c0-.12.016-.236.048-.345h5.335Z"/>
|
||||
<path fill="url(#s)" d="M10.907 2.257c.031.11.048.225.048.345v.329H5.523v-.329c0-.12.017-.235.049-.345h5.335Z"/>
|
||||
<path fill="url(#t)" d="M10.907 2.257c.031.11.048.225.048.345v.329H5.523v-.329c0-.12.017-.235.049-.345h5.335Z"/>
|
||||
<g filter="url(#u)">
|
||||
<path fill="url(#v)" d="M17.654 12.236h-3.116a.173.173 0 0 0-.176.17c0 1.255.904 2.3 2.096 2.518l.332-.349a3.125 3.125 0 0 0 .864-2.157v-.182Z"/>
|
||||
</g>
|
||||
<g filter="url(#w)">
|
||||
<path fill="url(#x)" d="M10.955 9.97H7.839a.173.173 0 0 0-.176.17c0 1.255.903 2.3 2.096 2.518l.332-.349a3.125 3.125 0 0 0 .864-2.156V9.97Z"/>
|
||||
</g>
|
||||
<g filter="url(#y)">
|
||||
<path fill="url(#z)" d="M13.243 18.21v-.05a3.424 3.424 0 0 0-3.856-3.397c-.511.986-.496 2.268.366 3.133.892.895 2.474.98 3.49.314Z"/>
|
||||
</g>
|
||||
<g filter="url(#A)">
|
||||
<path fill="url(#B)" d="M6.544 15.944v-.05a3.424 3.424 0 0 0-3.857-3.396c-.51.985-.495 2.267.366 3.132.892.896 2.475.98 3.49.314Z"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="b" x1="17.248" x2="13.193" y1="4.4" y2="16.827" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FDF2FF"/>
|
||||
<stop offset="1" stop-color="#FFECFB"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="c" x1="17.654" x2="15.615" y1="11.854" y2="11.854" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFCCF1"/>
|
||||
<stop offset="1" stop-color="#FFC5EF" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="f" x1="14.99" x2="13.818" y1="16.932" y2="15.721" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".209" stop-color="#EDB0DF"/>
|
||||
<stop offset="1" stop-color="#ECAAED" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="h" x1="10.549" x2="6.493" y1="2.134" y2="14.562" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FDF2FF"/>
|
||||
<stop offset="1" stop-color="#FFECFB"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="i" x1="10.954" x2="8.915" y1="9.588" y2="9.588" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFCCF1"/>
|
||||
<stop offset="1" stop-color="#FFC5EF" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="l" x1="8.29" x2="7.119" y1="14.666" y2="13.456" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".209" stop-color="#EDB0DF"/>
|
||||
<stop offset="1" stop-color="#ECAAED" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="m" x1="12.773" x2="16.915" y1="6.449" y2="6.449" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E95FDB"/>
|
||||
<stop offset="1" stop-color="#FF46CB"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="n" x1="12.223" x2="12.793" y1="6.449" y2="6.449" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9F4977"/>
|
||||
<stop offset="1" stop-color="#CA5284" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="o" x1="6.074" x2="10.216" y1="4.184" y2="4.184" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E95FDB"/>
|
||||
<stop offset="1" stop-color="#FF46CB"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="p" x1="5.523" x2="6.094" y1="4.184" y2="4.184" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9F4977"/>
|
||||
<stop offset="1" stop-color="#CA5284" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="q" x1="12.773" x2="16.915" y1="5.196" y2="5.196" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E95FDB"/>
|
||||
<stop offset="1" stop-color="#FF46CB"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="r" x1="12.223" x2="12.793" y1="5.196" y2="5.196" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9F4977"/>
|
||||
<stop offset="1" stop-color="#CA5284" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="s" x1="6.074" x2="10.216" y1="2.931" y2="2.931" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E95FDB"/>
|
||||
<stop offset="1" stop-color="#FF46CB"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="t" x1="5.523" x2="6.094" y1="2.931" y2="2.931" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9F4977"/>
|
||||
<stop offset="1" stop-color="#CA5284" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="d" cx="0" cy="0" r="1" gradientTransform="matrix(2.8125 0 0 6.01563 12.02 8.026)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFD1F5"/>
|
||||
<stop offset="1" stop-color="#FECAFF" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="e" cx="0" cy="0" r="1" gradientTransform="matrix(1.54348 -1.53472 2.30642 2.31958 11.806 16.12)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFC0FC"/>
|
||||
<stop offset="1" stop-color="#FFBCFC" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="j" cx="0" cy="0" r="1" gradientTransform="matrix(2.8125 0 0 6.01563 5.322 5.76)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFD1F5"/>
|
||||
<stop offset="1" stop-color="#FECAFF" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="k" cx="0" cy="0" r="1" gradientTransform="matrix(1.54347 -1.53473 2.30643 2.31957 5.107 13.854)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFC0FC"/>
|
||||
<stop offset="1" stop-color="#FFBCFC" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="v" cx="0" cy="0" r="1" gradientTransform="matrix(-.42911 2.50572 -2.20218 -.37713 16.437 12.236)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".147" stop-color="#FF52CF"/>
|
||||
<stop offset="1" stop-color="#FB3FFF"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="x" cx="0" cy="0" r="1" gradientTransform="matrix(-.42911 2.50572 -2.20218 -.37713 9.738 9.97)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".147" stop-color="#FF52CF"/>
|
||||
<stop offset="1" stop-color="#FB3FFF"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="z" cx="0" cy="0" r="1" gradientTransform="rotate(167.471 5.464 9.437) scale(4.54024 4.83245)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".268" stop-color="#FF4EE3"/>
|
||||
<stop offset=".92" stop-color="#D12396"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="B" cx="0" cy="0" r="1" gradientTransform="rotate(167.471 2.239 7.937) scale(4.54024 4.83245)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".268" stop-color="#FF4EE3"/>
|
||||
<stop offset=".92" stop-color="#D12396"/>
|
||||
</radialGradient>
|
||||
<filter id="a" width="8.808" height="15.23" x="9.045" y="3.418" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx=".2" dy="-.2"/>
|
||||
<feGaussianBlur stdDeviation=".3"/>
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
|
||||
<feColorMatrix values="0 0 0 0 0.522043 0 0 0 0 0.119948 0 0 0 0 0.5875 0 0 0 1 0"/>
|
||||
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
|
||||
</filter>
|
||||
<filter id="g" width="8.808" height="15.23" x="2.346" y="1.152" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx=".2" dy="-.2"/>
|
||||
<feGaussianBlur stdDeviation=".3"/>
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
|
||||
<feColorMatrix values="0 0 0 0 0.545098 0 0 0 0 0.219608 0 0 0 0 0.512549 0 0 0 1 0"/>
|
||||
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
|
||||
</filter>
|
||||
<filter id="u" width="3.541" height="2.688" x="14.112" y="12.236" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx="-.25"/>
|
||||
<feGaussianBlur stdDeviation=".25"/>
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
|
||||
<feColorMatrix values="0 0 0 0 0.976471 0 0 0 0 0.145098 0 0 0 0 0.743686 0 0 0 1 0"/>
|
||||
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
|
||||
</filter>
|
||||
<filter id="w" width="3.541" height="2.688" x="7.413" y="9.97" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx="-.25"/>
|
||||
<feGaussianBlur stdDeviation=".25"/>
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
|
||||
<feColorMatrix values="0 0 0 0 0.976471 0 0 0 0 0.145098 0 0 0 0 0.743686 0 0 0 1 0"/>
|
||||
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
|
||||
</filter>
|
||||
<filter id="y" width="4.448" height="4.112" x="9.045" y="14.536" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx=".25" dy="-.2"/>
|
||||
<feGaussianBlur stdDeviation=".25"/>
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
|
||||
<feColorMatrix values="0 0 0 0 0.906118 0 0 0 0 0.329412 0 0 0 0 1 0 0 0 1 0"/>
|
||||
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
|
||||
</filter>
|
||||
<filter id="A" width="4.448" height="4.112" x="2.346" y="12.27" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx=".25" dy="-.2"/>
|
||||
<feGaussianBlur stdDeviation=".25"/>
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
|
||||
<feColorMatrix values="0 0 0 0 0.906118 0 0 0 0 0.329412 0 0 0 0 1 0 0 0 1 0"/>
|
||||
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
3
src/assets/svg/swap-arrows.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.80333 4.8863C7.51044 5.17919 7.51044 5.65406 7.80333 5.94696C8.09622 6.23985 8.5711 6.23985 8.86399 5.94696L7.80333 4.8863ZM12.0837 1.66663L12.614 1.1363C12.3211 0.843403 11.8462 0.843403 11.5533 1.1363L12.0837 1.66663ZM15.3033 5.94696C15.5962 6.23985 16.0711 6.23985 16.364 5.94696C16.6569 5.65406 16.6569 5.17919 16.364 4.8863L15.3033 5.94696ZM11.3337 9.99996C11.3337 10.4142 11.6694 10.75 12.0837 10.75C12.4979 10.75 12.8337 10.4142 12.8337 9.99996H11.3337ZM12.1973 15.1136C12.4902 14.8207 12.4902 14.3459 12.1973 14.053C11.9044 13.7601 11.4296 13.7601 11.1367 14.053L12.1973 15.1136ZM7.91699 18.3333L7.38666 18.8636C7.52731 19.0043 7.71808 19.0833 7.91699 19.0833C8.1159 19.0833 8.30667 19.0043 8.44732 18.8636L7.91699 18.3333ZM4.69732 14.053C4.40443 13.7601 3.92956 13.7601 3.63666 14.053C3.34377 14.3459 3.34377 14.8207 3.63666 15.1136L4.69732 14.053ZM8.66699 10.8333C8.66699 10.4191 8.33121 10.0833 7.91699 10.0833C7.50278 10.0833 7.16699 10.4191 7.16699 10.8333H8.66699ZM8.86399 5.94696L12.614 2.19696L11.5533 1.1363L7.80333 4.8863L8.86399 5.94696ZM11.5533 2.19696L15.3033 5.94696L16.364 4.8863L12.614 1.1363L11.5533 2.19696ZM11.3337 1.66663V9.99996H12.8337V1.66663H11.3337ZM11.1367 14.053L7.38666 17.803L8.44732 18.8636L12.1973 15.1136L11.1367 14.053ZM8.44732 17.803L4.69732 14.053L3.63666 15.1136L7.38666 18.8636L8.44732 17.803ZM8.66699 18.3333L8.66699 10.8333H7.16699L7.16699 18.3333H8.66699Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.24453 18.0887C3.24331 19.0467 3.47372 19.7558 3.93576 20.2158C4.39658 20.6771 5.09574 20.904 6.03326 20.8967H8.11975C8.20693 20.8934 8.29386 20.9079 8.37521 20.9395C8.45656 20.9711 8.53062 21.019 8.5928 21.0802L10.0779 22.5484C10.7527 23.2226 11.4139 23.5578 12.0617 23.5541C12.7096 23.5504 13.3709 23.2152 14.0456 22.5484L15.5124 21.0802C15.5767 21.0182 15.6529 20.97 15.7365 20.9385C15.82 20.9069 15.9091 20.8927 15.9982 20.8967H18.0719C19.0192 20.8979 19.7251 20.6673 20.1896 20.2048C20.6541 19.7423 20.8864 19.0333 20.8864 18.0777V16.0021C20.8816 15.8222 20.9474 15.6476 21.0697 15.5157L22.5365 14.0475C23.2198 13.3758 23.559 12.7145 23.5541 12.0636C23.5492 11.4127 23.21 10.7508 22.5365 10.0779L21.0697 8.6097C20.9471 8.47802 20.8812 8.30329 20.8864 8.12336V6.04769C20.8851 5.09092 20.6547 4.3819 20.1951 3.92064C19.7355 3.45939 19.0278 3.22875 18.0719 3.22875H15.9982C15.9091 3.23242 15.8201 3.21807 15.7366 3.18653C15.6532 3.155 15.5769 3.10694 15.5124 3.04523L14.0456 1.57703C13.3709 0.902883 12.7096 0.567648 12.0617 0.571319C11.4139 0.574989 10.7527 0.910224 10.0779 1.57703L8.5928 3.04523C8.53043 3.10622 8.45638 3.15393 8.37508 3.18547C8.29377 3.21701 8.20689 3.23173 8.11975 3.22875H6.03326C5.08718 3.22998 4.38373 3.45877 3.92291 3.91513C3.4621 4.3715 3.23168 5.08235 3.23168 6.04769V8.12887C3.23683 8.3088 3.17096 8.48352 3.04833 8.6152L1.58154 10.0834C0.908042 10.7551 0.571289 11.417 0.571289 12.0691C0.571289 12.7213 0.912332 13.3844 1.59439 14.0585L3.06118 15.5267C3.18346 15.6586 3.24928 15.8332 3.24453 16.0131V18.0887Z" fill="#4C82FB"/>
|
||||
<path d="M3.24453 18.0887C3.24331 19.0467 3.47372 19.7558 3.93576 20.2158C4.39658 20.6771 5.09574 20.904 6.03326 20.8967H8.11975C8.20693 20.8934 8.29386 20.9079 8.37521 20.9395C8.45656 20.9711 8.53062 21.019 8.5928 21.0802L10.0779 22.5484C10.7527 23.2226 11.4139 23.5578 12.0617 23.5541C12.7096 23.5504 13.3709 23.2152 14.0456 22.5484L15.5124 21.0802C15.5767 21.0182 15.6529 20.97 15.7365 20.9385C15.82 20.9069 15.9091 20.8927 15.9982 20.8967H18.0719C19.0192 20.8979 19.7251 20.6673 20.1896 20.2048C20.6541 19.7423 20.8864 19.0333 20.8864 18.0777V16.0021C20.8816 15.8222 20.9474 15.6476 21.0697 15.5157L22.5365 14.0475C23.2198 13.3758 23.559 12.7145 23.5541 12.0636C23.5492 11.4127 23.21 10.7508 22.5365 10.0779L21.0697 8.6097C20.9471 8.47802 20.8812 8.30329 20.8864 8.12336V6.04769C20.8851 5.09092 20.6547 4.3819 20.1951 3.92064C19.7355 3.45939 19.0278 3.22875 18.0719 3.22875H15.9982C15.9091 3.23242 15.8201 3.21807 15.7366 3.18653C15.6532 3.155 15.5769 3.10694 15.5124 3.04523L14.0456 1.57703C13.3709 0.902883 12.7096 0.567648 12.0617 0.571319C11.4139 0.574989 10.7527 0.910224 10.0779 1.57703L8.5928 3.04523C8.53043 3.10622 8.45638 3.15393 8.37508 3.18547C8.29377 3.21701 8.20689 3.23173 8.11975 3.22875H6.03326C5.08718 3.22998 4.38373 3.45877 3.92291 3.91513C3.4621 4.3715 3.23168 5.08235 3.23168 6.04769V8.12887C3.23683 8.3088 3.17096 8.48352 3.04833 8.6152L1.58154 10.0834C0.908042 10.7551 0.571289 11.417 0.571289 12.0691C0.571289 12.7213 0.912332 13.3844 1.59439 14.0585L3.06118 15.5267C3.18346 15.6586 3.24928 15.8332 3.24453 16.0131V18.0887Z" fill="currentColor"/>
|
||||
<path d="M11.996 15.9909C11.7795 16.3208 11.4599 16.5064 11.0887 16.5064C10.7072 16.5064 10.4083 16.3517 10.1299 15.9909L7.69677 13.0216C7.5215 12.8051 7.42871 12.5783 7.42871 12.3309C7.42871 11.8154 7.82049 11.4133 8.32567 11.4133C8.63497 11.4133 8.8824 11.5267 9.12984 11.8463L11.0475 14.2897L15.1199 7.75329C15.3364 7.40275 15.6147 7.23779 15.924 7.23779C16.4086 7.23779 16.8622 7.57802 16.8622 8.0832C16.8622 8.32033 16.7385 8.56777 16.6045 8.78427L11.996 15.9909Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#99A1BD" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 300 B |
@@ -1,64 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import useCopyClipboard from 'hooks/useCopyClipboard'
|
||||
import React, { useCallback } from 'react'
|
||||
import { CheckCircle, Copy } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { LinkStyledButton } from 'theme'
|
||||
|
||||
const CopyIcon = styled(LinkStyledButton)`
|
||||
color: ${({ color, theme }) => color || theme.accentAction};
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
:hover,
|
||||
:active,
|
||||
:focus {
|
||||
text-decoration: none;
|
||||
color: ${({ color, theme }) => color || theme.accentAction};
|
||||
}
|
||||
`
|
||||
const StyledText = styled.span`
|
||||
margin-left: 0.25rem;
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const Copied = ({ iconSize }: { iconSize?: number }) => (
|
||||
<StyledText>
|
||||
<CheckCircle size={iconSize ?? '16'} />
|
||||
<StyledText>
|
||||
<Trans>Copied</Trans>
|
||||
</StyledText>
|
||||
</StyledText>
|
||||
)
|
||||
|
||||
const Icon = ({ iconSize }: { iconSize?: number }) => (
|
||||
<StyledText>
|
||||
<Copy size={iconSize ?? '16'} />
|
||||
</StyledText>
|
||||
)
|
||||
|
||||
interface BaseProps {
|
||||
toCopy: string
|
||||
color?: string
|
||||
iconSize?: number
|
||||
iconPosition?: 'left' | 'right'
|
||||
}
|
||||
export type CopyHelperProps = BaseProps & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof BaseProps>
|
||||
|
||||
export default function CopyHelper({ color, toCopy, children, iconSize, iconPosition }: CopyHelperProps) {
|
||||
const [isCopied, setCopied] = useCopyClipboard()
|
||||
const copy = useCallback(() => {
|
||||
setCopied(toCopy)
|
||||
}, [toCopy, setCopied])
|
||||
|
||||
return (
|
||||
<CopyIcon onClick={copy} color={color}>
|
||||
{iconPosition === 'left' ? isCopied ? <Copied iconSize={iconSize} /> : <Icon iconSize={iconSize} /> : null}
|
||||
{iconPosition === 'left' && <> </>}
|
||||
{isCopied ? '' : children}
|
||||
{iconPosition === 'right' && <> </>}
|
||||
{iconPosition === 'right' ? isCopied ? <Copied iconSize={iconSize} /> : <Icon iconSize={iconSize} /> : null}
|
||||
</CopyIcon>
|
||||
)
|
||||
}
|
||||
@@ -75,7 +75,7 @@ function ClaimSummary({ info: { recipient, uniAmountRaw } }: { info: ClaimTransa
|
||||
const { ENSName } = useENSName()
|
||||
return typeof uniAmountRaw === 'string' ? (
|
||||
<Trans>
|
||||
Claim <FormattedCurrencyAmount rawAmount={uniAmountRaw} symbol={'UNI'} decimals={18} sigFigs={4} /> for{' '}
|
||||
Claim <FormattedCurrencyAmount rawAmount={uniAmountRaw} symbol="UNI" decimals={18} sigFigs={4} /> for{' '}
|
||||
{ENSName ?? recipient}
|
||||
</Trans>
|
||||
) : (
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import CopyHelper from 'components/AccountDetails/Copy'
|
||||
import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsMetaMask } from 'connection/utils'
|
||||
import { Context, useCallback, useContext } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { ExternalLink as LinkIcon } from 'react-feather'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
import { removeConnectedWallet } from 'state/wallets/reducer'
|
||||
import { DefaultTheme } from 'styled-components/macro'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
|
||||
import { ReactComponent as Close } from '../../assets/images/x.svg'
|
||||
import { clearAllTransactions } from '../../state/transactions/reducer'
|
||||
import { ExternalLink, LinkStyledButton, ThemedText } from '../../theme'
|
||||
import { CopyHelper, ExternalLink, LinkStyledButton, ThemedText } from '../../theme'
|
||||
import { shortenAddress } from '../../utils'
|
||||
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
|
||||
import { ButtonSecondary } from '../Button'
|
||||
@@ -22,11 +21,11 @@ import { AutoRow } from '../Row'
|
||||
import Transaction from './Transaction'
|
||||
|
||||
const HeaderRow = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
${flexRowNoWrap};
|
||||
padding: 1rem 1rem;
|
||||
font-weight: 500;
|
||||
color: ${(props) => (props.color === 'blue' ? ({ theme }) => theme.deprecated_primary1 : 'inherit')};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
padding: 1rem;
|
||||
`};
|
||||
`
|
||||
@@ -62,21 +61,21 @@ const InfoCard = styled.div`
|
||||
`
|
||||
|
||||
const AccountGroupingRow = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
${flexColumnNoWrap};
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 400;
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
|
||||
div {
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
${flexColumnNoWrap};
|
||||
align-items: center;
|
||||
}
|
||||
`
|
||||
|
||||
const AccountSection = styled.div`
|
||||
padding: 0rem 1rem;
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`padding: 0rem 1rem 1.5rem 1rem;`};
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`padding: 0rem 1rem 1.5rem 1rem;`};
|
||||
`
|
||||
|
||||
const YourAccount = styled.div`
|
||||
@@ -92,7 +91,7 @@ const YourAccount = styled.div`
|
||||
`
|
||||
|
||||
const LowerSection = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap}
|
||||
${flexColumnNoWrap};
|
||||
padding: 1.5rem;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
@@ -129,12 +128,13 @@ const AccountControl = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const AddressLink = styled(ExternalLink)<{ hasENS: boolean; isENS: boolean }>`
|
||||
font-size: 0.825rem;
|
||||
const AddressLink = styled(ExternalLink)`
|
||||
color: ${({ theme }) => theme.deprecated_text3};
|
||||
margin-left: 1rem;
|
||||
font-size: 0.825rem;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
text-decoration: none !important;
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.deprecated_text2};
|
||||
}
|
||||
@@ -146,7 +146,7 @@ const CloseIcon = styled.div`
|
||||
top: 14px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
}
|
||||
`
|
||||
|
||||
@@ -164,7 +164,7 @@ const WalletName = styled.div`
|
||||
`
|
||||
|
||||
const TransactionListWrapper = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
${flexColumnNoWrap};
|
||||
`
|
||||
|
||||
const WalletAction = styled(ButtonSecondary)`
|
||||
@@ -207,7 +207,7 @@ export default function AccountDetails({
|
||||
const { chainId, account, connector } = useWeb3React()
|
||||
const connectionType = getConnection(connector).type
|
||||
|
||||
const theme = useContext(ThemeContext as Context<DefaultTheme>)
|
||||
const theme = useTheme()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const isMetaMask = getIsMetaMask()
|
||||
@@ -281,59 +281,21 @@ export default function AccountDetails({
|
||||
</AccountControl>
|
||||
</AccountGroupingRow>
|
||||
<AccountGroupingRow>
|
||||
{ENSName ? (
|
||||
<>
|
||||
<AccountControl>
|
||||
<div>
|
||||
{account && (
|
||||
<CopyHelper toCopy={account} iconPosition="left">
|
||||
<span style={{ marginLeft: '4px' }}>
|
||||
<Trans>Copy Address</Trans>
|
||||
</span>
|
||||
</CopyHelper>
|
||||
)}
|
||||
{chainId && account && (
|
||||
<AddressLink
|
||||
hasENS={!!ENSName}
|
||||
isENS={true}
|
||||
href={getExplorerLink(chainId, ENSName, ExplorerDataType.ADDRESS)}
|
||||
>
|
||||
<LinkIcon size={16} />
|
||||
<span style={{ marginLeft: '4px' }}>
|
||||
<Trans>View on Explorer</Trans>
|
||||
</span>
|
||||
</AddressLink>
|
||||
)}
|
||||
</div>
|
||||
</AccountControl>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<AccountControl>
|
||||
<div>
|
||||
{account && (
|
||||
<CopyHelper toCopy={account} iconPosition="left">
|
||||
<span style={{ marginLeft: '4px' }}>
|
||||
<Trans>Copy Address</Trans>
|
||||
</span>
|
||||
</CopyHelper>
|
||||
)}
|
||||
{chainId && account && (
|
||||
<AddressLink
|
||||
hasENS={!!ENSName}
|
||||
isENS={false}
|
||||
href={getExplorerLink(chainId, account, ExplorerDataType.ADDRESS)}
|
||||
>
|
||||
<LinkIcon size={16} />
|
||||
<span style={{ marginLeft: '4px' }}>
|
||||
<Trans>View on Explorer</Trans>
|
||||
</span>
|
||||
</AddressLink>
|
||||
)}
|
||||
</div>
|
||||
</AccountControl>
|
||||
</>
|
||||
)}
|
||||
<AccountControl>
|
||||
<div>
|
||||
{account && (
|
||||
<CopyHelper toCopy={account} gap={6} iconSize={16} fontSize={14}>
|
||||
<Trans>Copy Address</Trans>
|
||||
</CopyHelper>
|
||||
)}
|
||||
{chainId && account && (
|
||||
<AddressLink href={getExplorerLink(chainId, ENSName ?? account, ExplorerDataType.ADDRESS)}>
|
||||
<LinkIcon size={16} />
|
||||
<Trans>View on Explorer</Trans>
|
||||
</AddressLink>
|
||||
)}
|
||||
</div>
|
||||
</AccountControl>
|
||||
</AccountGroupingRow>
|
||||
</InfoCard>
|
||||
</YourAccount>
|
||||
@@ -341,7 +303,7 @@ export default function AccountDetails({
|
||||
</UpperSection>
|
||||
{!!pendingTransactions.length || !!confirmedTransactions.length ? (
|
||||
<LowerSection>
|
||||
<AutoRow mb={'1rem'} style={{ justifyContent: 'space-between' }}>
|
||||
<AutoRow mb="1rem" style={{ justifyContent: 'space-between' }}>
|
||||
<ThemedText.DeprecatedBody>
|
||||
<Trans>Recent Transactions</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
|
||||
88
src/components/AccountDetailsV2/LogoView.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { UNI_ADDRESS } from 'constants/addresses'
|
||||
import { TransactionInfo, TransactionType } from 'state/transactions/types'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
|
||||
import { nativeOnChain } from '../../constants/tokens'
|
||||
import { useCurrency } from '../../hooks/Tokens'
|
||||
import CurrencyLogo from '../Logo/CurrencyLogo'
|
||||
|
||||
const CurrencyWrap = styled.div`
|
||||
position: relative;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
`
|
||||
|
||||
const CurrencyWrapStyles = css`
|
||||
position: absolute;
|
||||
height: 24px;
|
||||
`
|
||||
|
||||
const CurrencyLogoWrap = styled.span<{ isCentered: boolean }>`
|
||||
${CurrencyWrapStyles};
|
||||
left: ${({ isCentered }) => (isCentered ? '50%' : '0')};
|
||||
top: ${({ isCentered }) => (isCentered ? '50%' : '0')};
|
||||
transform: ${({ isCentered }) => isCentered && 'translate(-50%, -50%)'};
|
||||
`
|
||||
const CurrencyLogoWrapTwo = styled.span`
|
||||
${CurrencyWrapStyles};
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
`
|
||||
|
||||
interface CurrencyPair {
|
||||
currencyId0: string | undefined
|
||||
currencyId1: string | undefined
|
||||
}
|
||||
|
||||
const getCurrency = ({ info, chainId }: { info: TransactionInfo; chainId: number | undefined }): CurrencyPair => {
|
||||
switch (info.type) {
|
||||
case TransactionType.ADD_LIQUIDITY_V3_POOL:
|
||||
case TransactionType.REMOVE_LIQUIDITY_V3:
|
||||
case TransactionType.CREATE_V3_POOL:
|
||||
const { baseCurrencyId, quoteCurrencyId } = info
|
||||
return { currencyId0: baseCurrencyId, currencyId1: quoteCurrencyId }
|
||||
case TransactionType.SWAP:
|
||||
const { inputCurrencyId, outputCurrencyId } = info
|
||||
return { currencyId0: inputCurrencyId, currencyId1: outputCurrencyId }
|
||||
case TransactionType.WRAP:
|
||||
const { unwrapped } = info
|
||||
const native = info.chainId ? nativeOnChain(info.chainId) : undefined
|
||||
const base = 'ETH'
|
||||
const wrappedCurrency = native?.wrapped.address ?? 'WETH'
|
||||
return { currencyId0: unwrapped ? wrappedCurrency : base, currencyId1: unwrapped ? base : wrappedCurrency }
|
||||
case TransactionType.COLLECT_FEES:
|
||||
const { currencyId0, currencyId1 } = info
|
||||
return { currencyId0, currencyId1 }
|
||||
case TransactionType.APPROVAL:
|
||||
return { currencyId0: info.tokenAddress, currencyId1: undefined }
|
||||
case TransactionType.CLAIM:
|
||||
const uniAddress = chainId ? UNI_ADDRESS[chainId] : undefined
|
||||
return { currencyId0: uniAddress, currencyId1: undefined }
|
||||
default:
|
||||
return { currencyId0: undefined, currencyId1: undefined }
|
||||
}
|
||||
}
|
||||
|
||||
const LogoView = ({ info }: { info: TransactionInfo }) => {
|
||||
const { chainId } = useWeb3React()
|
||||
const { currencyId0, currencyId1 } = getCurrency({ info, chainId })
|
||||
const currency0 = useCurrency(currencyId0)
|
||||
const currency1 = useCurrency(currencyId1)
|
||||
const isCentered = !(currency0 && currency1)
|
||||
|
||||
return (
|
||||
<CurrencyWrap>
|
||||
<CurrencyLogoWrap isCentered={isCentered}>
|
||||
<CurrencyLogo size="24px" currency={currency0} />
|
||||
</CurrencyLogoWrap>
|
||||
{!isCentered && (
|
||||
<CurrencyLogoWrapTwo>
|
||||
<CurrencyLogo size="24px" currency={currency1} />
|
||||
</CurrencyLogoWrapTwo>
|
||||
)}
|
||||
</CurrencyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
export default LogoView
|
||||
336
src/components/AccountDetailsV2/TransactionBody.tsx
Normal file
@@ -0,0 +1,336 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Fraction, TradeType } from '@uniswap/sdk-core'
|
||||
import JSBI from 'jsbi'
|
||||
import {
|
||||
AddLiquidityV3PoolTransactionInfo,
|
||||
ApproveTransactionInfo,
|
||||
ClaimTransactionInfo,
|
||||
CollectFeesTransactionInfo,
|
||||
ExactInputSwapTransactionInfo,
|
||||
ExactOutputSwapTransactionInfo,
|
||||
RemoveLiquidityV3TransactionInfo,
|
||||
TransactionInfo,
|
||||
TransactionType,
|
||||
WrapTransactionInfo,
|
||||
} from 'state/transactions/types'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { nativeOnChain } from '../../constants/tokens'
|
||||
import { useCurrency, useToken } from '../../hooks/Tokens'
|
||||
import useENSName from '../../hooks/useENSName'
|
||||
import { shortenAddress } from '../../utils'
|
||||
import { TransactionState } from './index'
|
||||
|
||||
const HighlightText = styled.span`
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-weight: 600;
|
||||
`
|
||||
|
||||
const BodyWrap = styled.div`
|
||||
line-height: 20px;
|
||||
`
|
||||
|
||||
interface ActionProps {
|
||||
pending: JSX.Element
|
||||
success: JSX.Element
|
||||
failed: JSX.Element
|
||||
transactionState: TransactionState
|
||||
}
|
||||
|
||||
const Action = ({ pending, success, failed, transactionState }: ActionProps) => {
|
||||
switch (transactionState) {
|
||||
case TransactionState.Failed:
|
||||
return failed
|
||||
case TransactionState.Success:
|
||||
return success
|
||||
default:
|
||||
return pending
|
||||
}
|
||||
}
|
||||
|
||||
const formatAmount = (amountRaw: string, decimals: number, sigFigs: number): string =>
|
||||
new Fraction(amountRaw, JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(decimals))).toSignificant(sigFigs)
|
||||
|
||||
const FailedText = ({ transactionState }: { transactionState: TransactionState }) =>
|
||||
transactionState === TransactionState.Failed ? <Trans>failed</Trans> : <span />
|
||||
|
||||
const FormattedCurrencyAmount = ({
|
||||
rawAmount,
|
||||
currencyId,
|
||||
}: {
|
||||
rawAmount: string
|
||||
currencyId: string
|
||||
sigFigs: number
|
||||
}) => {
|
||||
const currency = useCurrency(currencyId)
|
||||
|
||||
return currency ? (
|
||||
<HighlightText>
|
||||
{formatAmount(rawAmount, currency.decimals, /* sigFigs= */ 6)} {currency.symbol}
|
||||
</HighlightText>
|
||||
) : null
|
||||
}
|
||||
|
||||
const getRawAmounts = (
|
||||
info: ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo
|
||||
): { rawAmountFrom: string; rawAmountTo: string } => {
|
||||
return info.tradeType === TradeType.EXACT_INPUT
|
||||
? { rawAmountFrom: info.inputCurrencyAmountRaw, rawAmountTo: info.expectedOutputCurrencyAmountRaw }
|
||||
: { rawAmountFrom: info.expectedInputCurrencyAmountRaw, rawAmountTo: info.outputCurrencyAmountRaw }
|
||||
}
|
||||
|
||||
const SwapSummary = ({
|
||||
info,
|
||||
transactionState,
|
||||
}: {
|
||||
info: ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const actionProps = {
|
||||
transactionState,
|
||||
pending: <Trans>Swapping</Trans>,
|
||||
success: <Trans>Swapped</Trans>,
|
||||
failed: <Trans>Swap</Trans>,
|
||||
}
|
||||
const { rawAmountFrom, rawAmountTo } = getRawAmounts(info)
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} />{' '}
|
||||
<FormattedCurrencyAmount rawAmount={rawAmountFrom} currencyId={info.inputCurrencyId} sigFigs={2} />{' '}
|
||||
<Trans>for </Trans>{' '}
|
||||
<FormattedCurrencyAmount rawAmount={rawAmountTo} currencyId={info.outputCurrencyId} sigFigs={2} />{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const AddLiquidityV3PoolSummary = ({
|
||||
info,
|
||||
transactionState,
|
||||
}: {
|
||||
info: AddLiquidityV3PoolTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const { createPool, quoteCurrencyId, baseCurrencyId } = info
|
||||
|
||||
const actionProps = {
|
||||
transactionState,
|
||||
pending: <Trans>Adding</Trans>,
|
||||
success: <Trans>Added</Trans>,
|
||||
failed: <Trans>Add</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
{createPool ? (
|
||||
<CreateV3PoolSummary info={info} transactionState={transactionState} />
|
||||
) : (
|
||||
<>
|
||||
<Action {...actionProps} />{' '}
|
||||
<FormattedCurrencyAmount rawAmount={info.expectedAmountBaseRaw} currencyId={baseCurrencyId} sigFigs={2} />{' '}
|
||||
<Trans>and</Trans>{' '}
|
||||
<FormattedCurrencyAmount rawAmount={info.expectedAmountQuoteRaw} currencyId={quoteCurrencyId} sigFigs={2} />
|
||||
</>
|
||||
)}{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const RemoveLiquidityV3Summary = ({
|
||||
info: { baseCurrencyId, quoteCurrencyId, expectedAmountBaseRaw, expectedAmountQuoteRaw },
|
||||
transactionState,
|
||||
}: {
|
||||
info: RemoveLiquidityV3TransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const actionProps = {
|
||||
transactionState,
|
||||
pending: <Trans>Removing</Trans>,
|
||||
success: <Trans>Removed</Trans>,
|
||||
failed: <Trans>Remove</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} />{' '}
|
||||
<FormattedCurrencyAmount rawAmount={expectedAmountBaseRaw} currencyId={baseCurrencyId} sigFigs={2} />{' '}
|
||||
<Trans>and</Trans>{' '}
|
||||
<FormattedCurrencyAmount rawAmount={expectedAmountQuoteRaw} currencyId={quoteCurrencyId} sigFigs={2} />{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const CreateV3PoolSummary = ({
|
||||
info: { baseCurrencyId, quoteCurrencyId },
|
||||
transactionState,
|
||||
}: {
|
||||
info: AddLiquidityV3PoolTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const baseCurrency = useCurrency(baseCurrencyId)
|
||||
const quoteCurrency = useCurrency(quoteCurrencyId)
|
||||
const actionProps = {
|
||||
transactionState,
|
||||
pending: <Trans>Creating</Trans>,
|
||||
success: <Trans>Created</Trans>,
|
||||
failed: <Trans>Create</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} />{' '}
|
||||
<HighlightText>
|
||||
{baseCurrency?.symbol}/{quoteCurrency?.symbol}{' '}
|
||||
</HighlightText>
|
||||
<Trans>Pool</Trans> <FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const CollectFeesSummary = ({
|
||||
info,
|
||||
transactionState,
|
||||
}: {
|
||||
info: CollectFeesTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const { currencyId0, expectedCurrencyOwed0 = '0', expectedCurrencyOwed1 = '0', currencyId1 } = info
|
||||
const actionProps = {
|
||||
transactionState,
|
||||
pending: <Trans>Collecting</Trans>,
|
||||
success: <Trans>Collected</Trans>,
|
||||
failed: <Trans>Collect</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} />{' '}
|
||||
<FormattedCurrencyAmount rawAmount={expectedCurrencyOwed0} currencyId={currencyId0} sigFigs={2} />{' '}
|
||||
<Trans>and</Trans>{' '}
|
||||
<FormattedCurrencyAmount rawAmount={expectedCurrencyOwed1} currencyId={currencyId1} sigFigs={2} />{' '}
|
||||
<Trans>fees</Trans> <FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const ApprovalSummary = ({
|
||||
info,
|
||||
transactionState,
|
||||
}: {
|
||||
info: ApproveTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const token = useToken(info.tokenAddress)
|
||||
const actionProps = {
|
||||
transactionState,
|
||||
pending: <Trans>Approving</Trans>,
|
||||
success: <Trans>Approved</Trans>,
|
||||
failed: <Trans>Approve</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} /> <HighlightText>{token?.symbol}</HighlightText>{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const ClaimSummary = ({
|
||||
info: { recipient, uniAmountRaw },
|
||||
transactionState,
|
||||
}: {
|
||||
info: ClaimTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const { ENSName } = useENSName()
|
||||
const actionProps = {
|
||||
transactionState,
|
||||
pending: <Trans>Claiming</Trans>,
|
||||
success: <Trans>Claimed</Trans>,
|
||||
failed: <Trans>Claim</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
{uniAmountRaw && (
|
||||
<>
|
||||
<Action {...actionProps} />{' '}
|
||||
<HighlightText>
|
||||
{formatAmount(uniAmountRaw, 18, 4)}
|
||||
UNI{' '}
|
||||
</HighlightText>{' '}
|
||||
<Trans>for</Trans> <HighlightText>{ENSName ?? shortenAddress(recipient)}</HighlightText>
|
||||
</>
|
||||
)}{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const WrapSummary = ({
|
||||
info: { chainId, currencyAmountRaw, unwrapped },
|
||||
transactionState,
|
||||
}: {
|
||||
info: WrapTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const native = chainId ? nativeOnChain(chainId) : undefined
|
||||
const from = unwrapped ? native?.wrapped.symbol ?? 'WETH' : native?.symbol ?? 'ETH'
|
||||
const to = unwrapped ? native?.symbol ?? 'ETH' : native?.wrapped.symbol ?? 'WETH'
|
||||
|
||||
const amount = formatAmount(currencyAmountRaw, 18, 6)
|
||||
const actionProps = unwrapped
|
||||
? {
|
||||
transactionState,
|
||||
pending: <Trans>Unwrapping</Trans>,
|
||||
success: <Trans>Unwrapped</Trans>,
|
||||
failed: <Trans>Unwrap</Trans>,
|
||||
}
|
||||
: {
|
||||
transactionState,
|
||||
pending: <Trans>Wrapping</Trans>,
|
||||
success: <Trans>Wrapped</Trans>,
|
||||
failed: <Trans>Wrap</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} />{' '}
|
||||
<HighlightText>
|
||||
{amount} {from}
|
||||
</HighlightText>{' '}
|
||||
<Trans>to</Trans>{' '}
|
||||
<HighlightText>
|
||||
{amount} {to}
|
||||
</HighlightText>{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const TransactionBody = ({ info, transactionState }: { info: TransactionInfo; transactionState: TransactionState }) => {
|
||||
switch (info.type) {
|
||||
case TransactionType.SWAP:
|
||||
return <SwapSummary info={info} transactionState={transactionState} />
|
||||
case TransactionType.ADD_LIQUIDITY_V3_POOL:
|
||||
return <AddLiquidityV3PoolSummary info={info} transactionState={transactionState} />
|
||||
case TransactionType.REMOVE_LIQUIDITY_V3:
|
||||
return <RemoveLiquidityV3Summary info={info} transactionState={transactionState} />
|
||||
case TransactionType.WRAP:
|
||||
return <WrapSummary info={info} transactionState={transactionState} />
|
||||
case TransactionType.COLLECT_FEES:
|
||||
return <CollectFeesSummary info={info} transactionState={transactionState} />
|
||||
case TransactionType.APPROVAL:
|
||||
return <ApprovalSummary info={info} transactionState={transactionState} />
|
||||
case TransactionType.CLAIM:
|
||||
return <ClaimSummary info={info} transactionState={transactionState} />
|
||||
default:
|
||||
return <span />
|
||||
}
|
||||
}
|
||||
|
||||
export default TransactionBody
|
||||
90
src/components/AccountDetailsV2/index.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getChainInfoOrDefault } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { useMemo } from 'react'
|
||||
import { AlertTriangle, CheckCircle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { colors } from 'theme/colors'
|
||||
|
||||
import { TransactionDetails } from '../../state/transactions/types'
|
||||
import Loader from '../Loader'
|
||||
import LogoView from './LogoView'
|
||||
import TransactionBody from './TransactionBody'
|
||||
|
||||
export enum TransactionState {
|
||||
Pending,
|
||||
Success,
|
||||
Failed,
|
||||
}
|
||||
|
||||
const Grid = styled.a`
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
grid-template-columns: 44px auto 24px;
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
border-bottom: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
|
||||
padding: 12px;
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
transition: 250ms background-color ease;
|
||||
}
|
||||
`
|
||||
|
||||
const TextContainer = styled.span`
|
||||
font-size: 14px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
`
|
||||
|
||||
const IconStyleWrap = styled.span`
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
margin-left: auto;
|
||||
height: 16px;
|
||||
`
|
||||
|
||||
export const TransactionSummary = ({ transactionDetails }: { transactionDetails: TransactionDetails }) => {
|
||||
const { chainId = 1 } = useWeb3React()
|
||||
const tx = transactionDetails
|
||||
const { explorer } = getChainInfoOrDefault(chainId ? chainId : SupportedChainId.MAINNET)
|
||||
const { info, receipt, hash } = tx
|
||||
|
||||
const transactionState = useMemo(() => {
|
||||
const pending = !receipt
|
||||
const success = !pending && tx && (receipt?.status === 1 || typeof receipt?.status === 'undefined')
|
||||
const transactionState = pending
|
||||
? TransactionState.Pending
|
||||
: success
|
||||
? TransactionState.Success
|
||||
: TransactionState.Failed
|
||||
|
||||
return transactionState
|
||||
}, [receipt, tx])
|
||||
|
||||
const link = `${explorer}tx/${hash}`
|
||||
|
||||
return chainId ? (
|
||||
<Grid href={link} target="_blank">
|
||||
<LogoView info={info} />
|
||||
<TextContainer as="span">
|
||||
<TransactionBody info={info} transactionState={transactionState} />
|
||||
</TextContainer>
|
||||
{transactionState === TransactionState.Pending ? (
|
||||
<IconStyleWrap>
|
||||
<Loader />
|
||||
</IconStyleWrap>
|
||||
) : transactionState === TransactionState.Success ? (
|
||||
<IconStyleWrap>
|
||||
<CheckCircle color={colors.green200} size="16px" />
|
||||
</IconStyleWrap>
|
||||
) : (
|
||||
<IconStyleWrap>
|
||||
<AlertTriangle color={colors.gold200} size="16px" />
|
||||
</IconStyleWrap>
|
||||
)}
|
||||
</Grid>
|
||||
) : null
|
||||
}
|
||||
@@ -2,8 +2,9 @@ import { Trans } from '@lingui/macro'
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ChangeEvent, Context, ReactNode, useCallback, useContext } from 'react'
|
||||
import styled, { DefaultTheme, ThemeContext } from 'styled-components/macro'
|
||||
import { ChangeEvent, ReactNode, useCallback } from 'react'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { flexColumnNoWrap } from 'theme/styles'
|
||||
|
||||
import useENS from '../../hooks/useENS'
|
||||
import { ExternalLink, ThemedText } from '../../theme'
|
||||
@@ -12,7 +13,7 @@ import { AutoColumn } from '../Column'
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
const InputPanel = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap}
|
||||
${flexColumnNoWrap};
|
||||
position: relative;
|
||||
border-radius: 1.25rem;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg1};
|
||||
@@ -87,7 +88,7 @@ export default function AddressInputPanel({
|
||||
onChange: (value: string) => void
|
||||
}) {
|
||||
const { chainId } = useWeb3React()
|
||||
const theme = useContext(ThemeContext as Context<DefaultTheme>)
|
||||
const theme = useTheme()
|
||||
|
||||
const { address, loading, name } = useENS(value)
|
||||
|
||||
|
||||
333
src/components/AirdropModal/index.tsx
Normal file
@@ -0,0 +1,333 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import type { TransactionResponse } from '@ethersproject/providers'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import uniswapNftAirdropClaim from 'abis/uniswap-nft-airdrop-claim.json'
|
||||
import airdropBackgroundv2 from 'assets/images/airdopBackground.png'
|
||||
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
|
||||
import { OpacityHoverState } from 'components/Common'
|
||||
import Loader from 'components/Loader'
|
||||
import { UNISWAP_NFT_AIRDROP_CLAIM_ADDRESS } from 'constants/addresses'
|
||||
import { useContract } from 'hooks/useContract'
|
||||
import { ChevronRightIcon } from 'nft/components/icons'
|
||||
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
|
||||
import { CollectionRewardsFetcher } from 'nft/queries/genie/GetAirdorpMerkle'
|
||||
import { Airdrop, Rewards } from 'nft/types/airdrop'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import styled from 'styled-components/macro'
|
||||
import { CloseIcon, ThemedText } from 'theme'
|
||||
|
||||
import Modal from '../Modal'
|
||||
|
||||
const ModalWrap = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const Body = styled.div`
|
||||
padding: 28px 20px 20px 20px;
|
||||
`
|
||||
|
||||
const ClaimButton = styled(ThemeButton)`
|
||||
width: 100%;
|
||||
background-color: ${({ theme }) => theme.accentAction};
|
||||
border-radius: 12px;
|
||||
color: ${({ theme }) => theme.white};
|
||||
`
|
||||
|
||||
const Line = styled.div`
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: ${({ theme }) => theme.white};
|
||||
opacity: 0.24;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
`
|
||||
|
||||
const LinkWrap = styled.a`
|
||||
text-decoration: none;
|
||||
${OpacityHoverState}
|
||||
`
|
||||
|
||||
const ImageContainer = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const StyledImage = styled.img`
|
||||
width: 100%;
|
||||
height: 170px;
|
||||
`
|
||||
|
||||
const USDCLabel = styled.div`
|
||||
font-weight: 700;
|
||||
font-size: 36px;
|
||||
line-height: 44px;
|
||||
margin-top: 8px;
|
||||
color: white;
|
||||
`
|
||||
|
||||
const TextContainer = styled.div`
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
`
|
||||
|
||||
const RewardsDetailsContainer = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
const CurrencyText = styled.span`
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 14.5px;
|
||||
`
|
||||
|
||||
const ClaimContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
height: 380px;
|
||||
padding: 60px 28px;
|
||||
padding-bottom: 20px;
|
||||
`
|
||||
|
||||
const SuccessText = styled.div`
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
|
||||
const EtherscanLink = styled.a`
|
||||
text-decoration: none;
|
||||
|
||||
${OpacityHoverState}
|
||||
`
|
||||
|
||||
const CloseButton = styled(ThemeButton)`
|
||||
max-width: 68px;
|
||||
margin-top: auto;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
`
|
||||
|
||||
const SyledCloseIcon = styled(CloseIcon)`
|
||||
float: right;
|
||||
height: 24px;
|
||||
|
||||
${OpacityHoverState}
|
||||
`
|
||||
|
||||
const Error = styled.div`
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.accentCritical};
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
border-radius: 16px;
|
||||
padding: 12px 20px;
|
||||
font-size: 14px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
`
|
||||
|
||||
const ReactLinkWrap = styled.div`
|
||||
margin-bottom: 40px;
|
||||
`
|
||||
|
||||
const RewardsText = styled.span`
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: ${({ theme }) => theme.white};
|
||||
|
||||
&:first-child {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`
|
||||
|
||||
const RewardsInformationText = styled.span`
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
margin-bottom: 28px;
|
||||
`
|
||||
|
||||
const MainHeader = styled.span`
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
color: ${({ theme }) => theme.white};
|
||||
`
|
||||
|
||||
const EtherscanLinkWrap = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
enum RewardAmounts {
|
||||
tradingRewardAmount = 300,
|
||||
holderRewardAmount = 1000,
|
||||
combinedAmount = 1300,
|
||||
}
|
||||
|
||||
const AirdropModal = () => {
|
||||
const { account, provider } = useWeb3React()
|
||||
const [claim, setClaim] = useState<Rewards>()
|
||||
const [isClaimed, setIsClaimed] = useState(false)
|
||||
const [hash, setHash] = useState('')
|
||||
const [error, setError] = useState(false)
|
||||
const setIsClaimAvailable = useIsNftClaimAvailable((state) => state.setIsClaimAvailable)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [totalAmount, setTotalAmount] = useState(0)
|
||||
const isOpen = useModalIsOpen(ApplicationModal.UNISWAP_NFT_AIRDROP_CLAIM)
|
||||
const usdcAirdropToggle = useToggleModal(ApplicationModal.UNISWAP_NFT_AIRDROP_CLAIM)
|
||||
const contract = useContract(UNISWAP_NFT_AIRDROP_CLAIM_ADDRESS, uniswapNftAirdropClaim)
|
||||
|
||||
const displayError = () => {
|
||||
setIsSubmitting(false)
|
||||
setError(true)
|
||||
setTimeout(() => {
|
||||
setError(false)
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (account && provider && contract) {
|
||||
;(async () => {
|
||||
try {
|
||||
const { data } = await CollectionRewardsFetcher(account)
|
||||
const claim = data.find((claim) => claim?.rewardType === Airdrop.GENIE_UNISWAP_USDC_AIRDROP)
|
||||
|
||||
if (!claim) return
|
||||
|
||||
const [isClaimed] = await contract.connect(provider).functions.isClaimed(claim?.index)
|
||||
|
||||
if (claim && isClaimed === false) {
|
||||
const usdAmount = BigNumber.from(claim.amount).div(10 ** 6)
|
||||
setClaim(claim)
|
||||
setTotalAmount(usdAmount.toNumber())
|
||||
setIsClaimAvailable(true)
|
||||
}
|
||||
} catch (err) {
|
||||
displayError()
|
||||
}
|
||||
})()
|
||||
}
|
||||
}, [account, contract, provider, setIsClaimAvailable])
|
||||
|
||||
const makeClaim = async () => {
|
||||
try {
|
||||
if (contract && claim && claim.amount && claim.merkleProof && provider) {
|
||||
setIsSubmitting(true)
|
||||
|
||||
const response: TransactionResponse = await contract
|
||||
.connect(provider?.getSigner())
|
||||
.functions.claim(claim.index, account, claim?.amount, claim?.merkleProof)
|
||||
|
||||
setHash(response.hash)
|
||||
setIsSubmitting(false)
|
||||
setIsClaimed(true)
|
||||
setIsClaimAvailable(false)
|
||||
}
|
||||
} catch (err) {
|
||||
setIsSubmitting(false)
|
||||
displayError()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal hideBorder isOpen={isOpen} onDismiss={usdcAirdropToggle} maxHeight={90} maxWidth={400}>
|
||||
<ModalWrap>
|
||||
{isClaimed ? (
|
||||
<ClaimContainer>
|
||||
<ThemedText.HeadlineSmall>Congratulations!</ThemedText.HeadlineSmall>
|
||||
<SuccessText>
|
||||
You have successfully claimed {totalAmount} USDC. Thank you for supporting Genie.xyz.
|
||||
</SuccessText>
|
||||
<EtherscanLink href={`https://etherscan.io/tx/${hash}`} target="_blank">
|
||||
<ThemedText.Link>
|
||||
<EtherscanLinkWrap>
|
||||
<span>Etherscan</span>
|
||||
<ChevronRightIcon />
|
||||
</EtherscanLinkWrap>
|
||||
</ThemedText.Link>
|
||||
</EtherscanLink>
|
||||
|
||||
<CloseButton size={ButtonSize.medium} emphasis={ButtonEmphasis.medium} onClick={usdcAirdropToggle}>
|
||||
Close
|
||||
</CloseButton>
|
||||
</ClaimContainer>
|
||||
) : (
|
||||
<>
|
||||
<ImageContainer>
|
||||
<TextContainer>
|
||||
<SyledCloseIcon onClick={usdcAirdropToggle} stroke="white" />
|
||||
<MainHeader>Uniswap NFT Airdrop</MainHeader>
|
||||
<USDCLabel>{totalAmount} USDC</USDCLabel>
|
||||
<Line />
|
||||
<RewardsDetailsContainer>
|
||||
<RewardsText>Trading rewards</RewardsText>{' '}
|
||||
<CurrencyText>
|
||||
{totalAmount === RewardAmounts.tradingRewardAmount || totalAmount === RewardAmounts.combinedAmount
|
||||
? `${RewardAmounts.tradingRewardAmount} USDC`
|
||||
: '0'}
|
||||
</CurrencyText>
|
||||
</RewardsDetailsContainer>
|
||||
<RewardsDetailsContainer>
|
||||
<RewardsText>Genie NFT holder rewards</RewardsText>{' '}
|
||||
<CurrencyText>
|
||||
{totalAmount !== RewardAmounts.tradingRewardAmount
|
||||
? `${RewardAmounts.holderRewardAmount} USDC`
|
||||
: '0'}
|
||||
</CurrencyText>
|
||||
</RewardsDetailsContainer>
|
||||
</TextContainer>
|
||||
<StyledImage src={airdropBackgroundv2} />
|
||||
</ImageContainer>
|
||||
<Body>
|
||||
<RewardsInformationText>
|
||||
As a long time supporter of Genie, you’ve been awarded {totalAmount} USDC tokens.
|
||||
</RewardsInformationText>
|
||||
<ReactLinkWrap>
|
||||
<LinkWrap href="https://uniswap.org/blog/uniswap-nft-aggregator-announcement" target="_blank">
|
||||
<ThemedText.Link>Read more about Uniswap NFT.</ThemedText.Link>
|
||||
</LinkWrap>
|
||||
</ReactLinkWrap>
|
||||
|
||||
{error && (
|
||||
<Error>
|
||||
<AlertTriangle />
|
||||
Claim USDC failed. Please try again later
|
||||
</Error>
|
||||
)}
|
||||
|
||||
<ClaimButton
|
||||
onClick={makeClaim}
|
||||
size={ButtonSize.medium}
|
||||
emphasis={ButtonEmphasis.medium}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting && <Loader stroke="white" />}
|
||||
<span>Claim{isSubmitting && 'ing'} USDC</span>
|
||||
</ClaimButton>
|
||||
</Body>
|
||||
</>
|
||||
)}
|
||||
</ModalWrap>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AirdropModal
|
||||
@@ -1,57 +0,0 @@
|
||||
import { createContext, memo, PropsWithChildren, useContext, useEffect, useMemo } from 'react'
|
||||
|
||||
import { sendAnalyticsEvent } from '.'
|
||||
import { ElementName, EventName, ModalName, PageName, SectionName } from './constants'
|
||||
|
||||
export interface ITraceContext {
|
||||
// Highest order context: eg Swap or Explore.
|
||||
page?: PageName
|
||||
|
||||
// Enclosed section name. For contexts with modals, refers to the
|
||||
// section of the page from which the user triggered the modal.
|
||||
section?: SectionName
|
||||
|
||||
modal?: ModalName
|
||||
|
||||
// Element name mostly used to identify events sources
|
||||
// Does not need to be unique given the higher order page and section.
|
||||
element?: ElementName
|
||||
}
|
||||
|
||||
export const TraceContext = createContext<ITraceContext>({})
|
||||
|
||||
type TraceProps = {
|
||||
shouldLogImpression?: boolean // whether to log impression on mount
|
||||
name?: EventName
|
||||
properties?: Record<string, unknown>
|
||||
} & ITraceContext
|
||||
|
||||
/**
|
||||
* Sends an analytics event on mount (if shouldLogImpression is set),
|
||||
* and propagates the context to child traces.
|
||||
*/
|
||||
export const Trace = memo(
|
||||
({ shouldLogImpression, name, children, page, section, element, properties }: PropsWithChildren<TraceProps>) => {
|
||||
const parentTrace = useContext(TraceContext)
|
||||
|
||||
const combinedProps = useMemo(
|
||||
() => ({
|
||||
...parentTrace,
|
||||
...Object.fromEntries(Object.entries({ page, section, element }).filter(([_, v]) => v !== undefined)),
|
||||
}),
|
||||
[element, parentTrace, page, section]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldLogImpression) {
|
||||
sendAnalyticsEvent(name ?? EventName.PAGE_VIEWED, { ...combinedProps, ...properties })
|
||||
}
|
||||
// Impressions should only be logged on mount.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
return <TraceContext.Provider value={combinedProps}>{children}</TraceContext.Provider>
|
||||
}
|
||||
)
|
||||
|
||||
Trace.displayName = 'Trace'
|
||||
@@ -1,75 +0,0 @@
|
||||
import { Children, cloneElement, isValidElement, memo, PropsWithChildren, SyntheticEvent } from 'react'
|
||||
|
||||
import { sendAnalyticsEvent } from '.'
|
||||
import { Event, EventName } from './constants'
|
||||
import { ITraceContext, Trace, TraceContext } from './Trace'
|
||||
|
||||
type TraceEventProps = {
|
||||
events: Event[]
|
||||
name: EventName
|
||||
properties?: Record<string, unknown>
|
||||
shouldLogImpression?: boolean
|
||||
} & ITraceContext
|
||||
|
||||
/**
|
||||
* Analytics instrumentation component that wraps event callbacks with logging logic.
|
||||
*
|
||||
* @example
|
||||
* <TraceEvent events={[Event.onClick]} element={ElementName.SWAP_BUTTON}>
|
||||
* <Button onClick={() => console.log('clicked')}>Click me</Button>
|
||||
* </TraceEvent>
|
||||
*/
|
||||
export const TraceEvent = memo((props: PropsWithChildren<TraceEventProps>) => {
|
||||
const { shouldLogImpression, name, properties, events, children, ...traceProps } = props
|
||||
|
||||
return (
|
||||
<Trace {...traceProps}>
|
||||
<TraceContext.Consumer>
|
||||
{(traceContext) =>
|
||||
Children.map(children, (child) => {
|
||||
if (!isValidElement(child)) {
|
||||
return child
|
||||
}
|
||||
|
||||
// For each child, augment event handlers defined in `events` with event tracing.
|
||||
return cloneElement(
|
||||
child,
|
||||
getEventHandlers(child, traceContext, events, name, properties, shouldLogImpression)
|
||||
)
|
||||
})
|
||||
}
|
||||
</TraceContext.Consumer>
|
||||
</Trace>
|
||||
)
|
||||
})
|
||||
|
||||
TraceEvent.displayName = 'TraceEvent'
|
||||
|
||||
/**
|
||||
* Given a set of child element and event props, returns a spreadable
|
||||
* object of the event handlers augmented with analytics logging.
|
||||
*/
|
||||
function getEventHandlers(
|
||||
child: React.ReactElement,
|
||||
traceContext: ITraceContext,
|
||||
events: Event[],
|
||||
name: EventName,
|
||||
properties?: Record<string, unknown>,
|
||||
shouldLogImpression = true
|
||||
) {
|
||||
const eventHandlers: Partial<Record<Event, (e: SyntheticEvent<Element, Event>) => void>> = {}
|
||||
|
||||
for (const event of events) {
|
||||
eventHandlers[event] = (eventHandlerArgs: unknown) => {
|
||||
// call child event handler with original arguments, must be in array
|
||||
const args = Array.isArray(eventHandlerArgs) ? eventHandlerArgs : [eventHandlerArgs]
|
||||
child.props[event]?.apply(child, args)
|
||||
|
||||
// augment handler with analytics logging
|
||||
if (shouldLogImpression) sendAnalyticsEvent(name, { ...traceContext, ...properties, action: event })
|
||||
}
|
||||
}
|
||||
|
||||
// return a spreadable event handler object
|
||||
return eventHandlers
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
/**
|
||||
* Event names that can occur in this application.
|
||||
*
|
||||
* Subject to change as new features are added and new events are defined
|
||||
* and logged.
|
||||
*/
|
||||
export enum EventName {
|
||||
APP_LOADED = 'Application Loaded',
|
||||
APPROVE_TOKEN_TXN_SUBMITTED = 'Approve Token Transaction Submitted',
|
||||
CONNECT_WALLET_BUTTON_CLICKED = 'Connect Wallet Button Clicked',
|
||||
EXPLORE_TOKEN_ROW_CLICKED = 'Explore Token Row Clicked',
|
||||
PAGE_VIEWED = 'Page Viewed',
|
||||
SWAP_AUTOROUTER_VISUALIZATION_EXPANDED = 'Swap Autorouter Visualization Expanded',
|
||||
SWAP_DETAILS_EXPANDED = 'Swap Details Expanded',
|
||||
SWAP_MAX_TOKEN_AMOUNT_SELECTED = 'Swap Max Token Amount Selected',
|
||||
SWAP_PRICE_UPDATE_ACKNOWLEDGED = 'Swap Price Update Acknowledged',
|
||||
SWAP_QUOTE_RECEIVED = 'Swap Quote Received',
|
||||
SWAP_SUBMITTED = 'Swap Submitted',
|
||||
SWAP_TOKENS_REVERSED = 'Swap Tokens Reversed',
|
||||
SWAP_TRANSACTION_COMPLETED = 'Swap Transaction Completed',
|
||||
TOKEN_IMPORTED = 'Token Imported',
|
||||
TOKEN_SELECTED = 'Token Selected',
|
||||
TOKEN_SELECTOR_OPENED = 'Token Selector Opened',
|
||||
WALLET_CONNECT_TXN_COMPLETED = 'Wallet Connect Transaction Completed',
|
||||
WALLET_SELECTED = 'Wallet Selected',
|
||||
WRAP_TOKEN_TXN_SUBMITTED = 'Wrap Token Transaction Submitted',
|
||||
// alphabetize additional event names.
|
||||
}
|
||||
|
||||
export enum CUSTOM_USER_PROPERTIES {
|
||||
ALL_WALLET_ADDRESSES_CONNECTED = 'all_wallet_addresses_connected',
|
||||
ALL_WALLET_CHAIN_IDS = 'all_wallet_chain_ids',
|
||||
BROWSER = 'browser',
|
||||
DARK_MODE = 'is_dark_mode',
|
||||
EXPERT_MODE = 'is_expert_mode',
|
||||
SCREEN_RESOLUTION_HEIGHT = 'screen_resolution_height',
|
||||
SCREEN_RESOLUTION_WIDTH = 'screen_resolution_width',
|
||||
WALLET_ADDRESS = 'wallet_address',
|
||||
WALLET_NATIVE_CURRENCY_BALANCE_USD = 'wallet_native_currency_balance_usd',
|
||||
WALLET_TOKENS_ADDRESSES = 'wallet_tokens_addresses',
|
||||
WALLET_TOKENS_SYMBOLS = 'wallet_tokens_symbols',
|
||||
WALLET_TYPE = 'wallet_type',
|
||||
}
|
||||
|
||||
export enum CUSTOM_USER_PROPERTY_SUFFIXES {
|
||||
WALLET_TOKEN_AMOUNT_SUFFIX = '_token_amount',
|
||||
}
|
||||
|
||||
export enum BROWSER {
|
||||
FIREFOX = 'Mozilla Firefox',
|
||||
SAMSUNG = 'Samsung Internet',
|
||||
OPERA = 'Opera',
|
||||
INTERNET_EXPLORER = 'Microsoft Internet Explorer',
|
||||
EDGE = 'Microsoft Edge (Legacy)',
|
||||
EDGE_CHROMIUM = 'Microsoft Edge (Chromium)',
|
||||
CHROME = 'Google Chrome or Chromium',
|
||||
SAFARI = 'Apple Safari',
|
||||
UNKNOWN = 'unknown',
|
||||
}
|
||||
|
||||
export enum WALLET_CONNECTION_RESULT {
|
||||
SUCCEEDED = 'Succeeded',
|
||||
FAILED = 'Failed',
|
||||
}
|
||||
|
||||
export const NATIVE_CHAIN_ID = 'NATIVE'
|
||||
|
||||
export enum SWAP_PRICE_UPDATE_USER_RESPONSE {
|
||||
ACCEPTED = 'Accepted',
|
||||
REJECTED = 'Rejected',
|
||||
}
|
||||
|
||||
/**
|
||||
* Known pages in the app. Highest order context.
|
||||
*/
|
||||
export enum PageName {
|
||||
EXPLORE_PAGE = 'explore-page',
|
||||
POOL_PAGE = 'pool-page',
|
||||
SWAP_PAGE = 'swap-page',
|
||||
VOTE_PAGE = 'vote-page',
|
||||
// alphabetize additional page names.
|
||||
}
|
||||
|
||||
/**
|
||||
* Sections. Disambiguates low-level elements that may share a name.
|
||||
* eg a `back` button in a modal will have the same `element`,
|
||||
* but a different `section`.
|
||||
*/
|
||||
export enum SectionName {
|
||||
CURRENCY_INPUT_PANEL = 'swap-currency-input',
|
||||
CURRENCY_OUTPUT_PANEL = 'swap-currency-output',
|
||||
// alphabetize additional section names.
|
||||
}
|
||||
|
||||
/** Known modals for analytics purposes. */
|
||||
export enum ModalName {
|
||||
CONFIRM_SWAP = 'confirm-swap-modal',
|
||||
TOKEN_SELECTOR = 'token-selector-modal',
|
||||
// alphabetize additional modal names.
|
||||
}
|
||||
|
||||
/**
|
||||
* Known element names for analytics purposes.
|
||||
* Use to identify low-level components given a TraceContext
|
||||
*/
|
||||
export enum ElementName {
|
||||
AUTOROUTER_VISUALIZATION_ROW = 'expandable-autorouter-visualization-row',
|
||||
COMMON_BASES_CURRENCY_BUTTON = 'common-bases-currency-button',
|
||||
CONFIRM_SWAP_BUTTON = 'confirm-swap-or-send',
|
||||
CONNECT_WALLET_BUTTON = 'connect-wallet-button',
|
||||
IMPORT_TOKEN_BUTTON = 'import-token-button',
|
||||
MAX_TOKEN_AMOUNT_BUTTON = 'max-token-amount-button',
|
||||
PRICE_UPDATE_ACCEPT_BUTTON = 'price-update-accept-button',
|
||||
SWAP_BUTTON = 'swap-button',
|
||||
SWAP_DETAILS_DROPDOWN = 'swap-details-dropdown',
|
||||
SWAP_TOKENS_REVERSE_ARROW_BUTTON = 'swap-tokens-reverse-arrow-button',
|
||||
TOKEN_SELECTOR_ROW = 'token-selector-row',
|
||||
WALLET_TYPE_OPTION = 'wallet-type-option',
|
||||
// alphabetize additional element names.
|
||||
}
|
||||
|
||||
/**
|
||||
* Known events that trigger callbacks.
|
||||
* @example
|
||||
* <TraceEvent events={[Event.onClick]} element={name}>
|
||||
*/
|
||||
export enum Event {
|
||||
onClick = 'onClick',
|
||||
onKeyPress = 'onKeyPress',
|
||||
onSelect = 'onSelect',
|
||||
// alphabetize additional events.
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
import { Identify, identify, init, track } from '@amplitude/analytics-browser'
|
||||
import { isDevelopmentEnv } from 'utils/env'
|
||||
|
||||
/**
|
||||
* Initializes Amplitude with API key for project.
|
||||
*
|
||||
* Uniswap has two Amplitude projects: test and production. You must be a
|
||||
* member of the organization on Amplitude to view details.
|
||||
*/
|
||||
export function initializeAnalytics() {
|
||||
if (isDevelopmentEnv()) return
|
||||
|
||||
const API_KEY = process.env.REACT_APP_AMPLITUDE_KEY
|
||||
if (typeof API_KEY === 'undefined') {
|
||||
throw new Error(`REACT_APP_AMPLITUDE_KEY must be a defined environment variable`)
|
||||
}
|
||||
|
||||
init(
|
||||
API_KEY,
|
||||
/* userId= */ undefined, // User ID should be undefined to let Amplitude default to Device ID
|
||||
/* options= */ {
|
||||
// See documentation: https://www.docs.developers.amplitude.com/data/sdks/javascript/#track-referrers
|
||||
includeReferrer: true,
|
||||
// See documentation: https://www.docs.developers.amplitude.com/data/sdks/javascript/#track-utm-parameters
|
||||
includeUtm: true,
|
||||
// Disable tracking of private user information by Amplitude
|
||||
trackingOptions: {
|
||||
ipAddress: false,
|
||||
carrier: false,
|
||||
city: false,
|
||||
region: false,
|
||||
dma: false, // designated market area
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/** Sends an event to Amplitude. */
|
||||
export function sendAnalyticsEvent(eventName: string, eventProperties?: Record<string, unknown>) {
|
||||
if (isDevelopmentEnv()) {
|
||||
console.log(`[amplitude(${eventName})]: ${JSON.stringify(eventProperties)}`)
|
||||
return
|
||||
}
|
||||
|
||||
track(eventName, eventProperties)
|
||||
}
|
||||
|
||||
type Value = string | number | boolean | string[] | number[]
|
||||
|
||||
/**
|
||||
* Class that exposes methods to mutate the User Model's properties in
|
||||
* Amplitude that represents the current session's user.
|
||||
*
|
||||
* See https://help.amplitude.com/hc/en-us/articles/115002380567-User-properties-and-event-properties
|
||||
* for details.
|
||||
*/
|
||||
class UserModel {
|
||||
private log(method: string, ...parameters: unknown[]) {
|
||||
console.debug(`[amplitude(Identify)]: ${method}(${parameters})`)
|
||||
}
|
||||
|
||||
private call(mutate: (event: Identify) => Identify) {
|
||||
if (isDevelopmentEnv()) {
|
||||
const log = (_: Identify, method: string) => this.log.bind(this, method)
|
||||
mutate(new Proxy(new Identify(), { get: log }))
|
||||
return
|
||||
}
|
||||
identify(mutate(new Identify()))
|
||||
}
|
||||
|
||||
set(key: string, value: Value) {
|
||||
this.call((event) => event.set(key, value))
|
||||
}
|
||||
|
||||
setOnce(key: string, value: Value) {
|
||||
this.call((event) => event.setOnce(key, value))
|
||||
}
|
||||
|
||||
add(key: string, value: number) {
|
||||
this.call((event) => event.add(key, value))
|
||||
}
|
||||
|
||||
postInsert(key: string, value: string | number) {
|
||||
this.call((event) => event.postInsert(key, value))
|
||||
}
|
||||
|
||||
remove(key: string, value: string | number) {
|
||||
this.call((event) => event.remove(key, value))
|
||||
}
|
||||
}
|
||||
|
||||
export const user = new UserModel()
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
|
||||
import { NATIVE_CHAIN_ID } from './constants'
|
||||
|
||||
export const getDurationUntilTimestampSeconds = (futureTimestampInSecondsSinceEpoch?: number): number | undefined => {
|
||||
if (!futureTimestampInSecondsSinceEpoch) return undefined
|
||||
return futureTimestampInSecondsSinceEpoch - new Date().getTime() / 1000
|
||||
}
|
||||
|
||||
export const getDurationFromDateMilliseconds = (start: Date): number => {
|
||||
return new Date().getTime() - start.getTime()
|
||||
}
|
||||
|
||||
export const formatToDecimal = (
|
||||
intialNumberObject: Percent | CurrencyAmount<Token | Currency>,
|
||||
decimalPlace: number
|
||||
): number => parseFloat(intialNumberObject.toFixed(decimalPlace))
|
||||
|
||||
export const getTokenAddress = (currency: Currency) => (currency.isNative ? NATIVE_CHAIN_ID : currency.address)
|
||||
|
||||
export const formatPercentInBasisPointsNumber = (percent: Percent): number => parseFloat(percent.toFixed(2)) * 100
|
||||
|
||||
export const formatPercentNumber = (percent: Percent): number => parseFloat(percent.toFixed(2))
|
||||
@@ -1,7 +1,6 @@
|
||||
import { readableColor } from 'polished'
|
||||
import { PropsWithChildren } from 'react'
|
||||
import styled, { DefaultTheme } from 'styled-components/macro'
|
||||
import { Color } from 'theme/styled'
|
||||
|
||||
export enum BadgeVariant {
|
||||
DEFAULT = 'DEFAULT',
|
||||
@@ -17,7 +16,7 @@ interface BadgeProps {
|
||||
variant?: BadgeVariant
|
||||
}
|
||||
|
||||
function pickBackgroundColor(variant: BadgeVariant | undefined, theme: DefaultTheme): Color {
|
||||
function pickBackgroundColor(variant: BadgeVariant | undefined, theme: DefaultTheme): string {
|
||||
switch (variant) {
|
||||
case BadgeVariant.NEGATIVE:
|
||||
return theme.deprecated_error
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ReactNode, useMemo } from 'react'
|
||||
|
||||
const BLOCKED_ADDRESSES: string[] = [
|
||||
'0x7Db418b5D567A4e0E8c59Ad71BE1FcE48f3E6107',
|
||||
'0x72a5843cc08275C8171E582972Aa4fDa8C397B2A',
|
||||
'0x7F19720A857F834887FC9A7bC0a0fBe7Fc7f8102',
|
||||
'0xA7e5d5A720f06526557c513402f2e6B5fA20b008',
|
||||
'0x1da5821544e25c636c1417Ba96Ade4Cf6D2f9B5A',
|
||||
'0x9F4cda013E354b8fC285BF4b9A60460cEe7f7Ea9',
|
||||
'0x19Aa5Fe80D33a56D56c78e82eA5E50E5d80b4Dff',
|
||||
'0x2f389cE8bD8ff92De3402FFCe4691d17fC4f6535',
|
||||
'0xe7aa314c77F4233C18C6CC84384A9247c0cf367B',
|
||||
'0x7F367cC41522cE07553e823bf3be79A889DEbe1B',
|
||||
'0xd882cFc20F52f2599D84b8e8D58C7FB62cfE344b',
|
||||
'0x901bb9583b24D97e995513C6778dc6888AB6870e',
|
||||
'0x8576aCC5C05D6Ce88f4e49bf65BdF0C62F91353C',
|
||||
'0xC8a65Fadf0e0dDAf421F28FEAb69Bf6E2E589963',
|
||||
'0x308eD4B7b49797e1A98D3818bFF6fe5385410370',
|
||||
'0x67d40EE1A85bf4a4Bb7Ffae16De985e8427B',
|
||||
'0x6f1ca141a28907f78ebaa64fb83a9088b02a83',
|
||||
'0x6acdfba02d390b97ac2b2d42a63e85293bcc1',
|
||||
'0x48549a34ae37b12f6a30566245176994e17c6',
|
||||
'0x5512d943ed1f7c8a43f3435c85f7ab68b30121',
|
||||
'0xC455f7fd3e0e12afd51fba5c106909934D8A0e4a',
|
||||
'0x3CBdeD43EFdAf0FC77b9C55F6fC9988fCC9b757d',
|
||||
'0x67d40EE1A85bf4a4Bb7Ffae16De985e8427B6b45',
|
||||
'0x6F1cA141A28907F78Ebaa64fb83A9088b02A8352',
|
||||
'0x6aCDFBA02D390b97Ac2b2d42A63E85293BCc160e',
|
||||
'0x48549a34ae37b12f6a30566245176994e17c6b4a',
|
||||
'0x5512d943ed1f7c8a43f3435c85f7ab68b30121b0',
|
||||
'0xC455f7fd3e0e12afd51fba5c106909934D8A0e4a',
|
||||
'0x629e7Da20197a5429d30da36E77d06CdF796b71A',
|
||||
'0x7FF9cFad3877F21d41Da833E2F775dB0569eE3D9',
|
||||
'0x098B716B8Aaf21512996dC57EB0615e2383E2f96',
|
||||
'0xfEC8A60023265364D066a1212fDE3930F6Ae8da7',
|
||||
]
|
||||
|
||||
export default function Blocklist({ children }: { children: ReactNode }) {
|
||||
const { account } = useWeb3React()
|
||||
const blocked: boolean = useMemo(() => Boolean(account && BLOCKED_ADDRESSES.indexOf(account) !== -1), [account])
|
||||
if (blocked) {
|
||||
return (
|
||||
<div>
|
||||
<Trans>Blocked address</Trans>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return <>{children}</>
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { darken } from 'polished'
|
||||
import { Check, ChevronDown } from 'react-feather'
|
||||
import { Button as RebassButton, ButtonProps as ButtonPropsOriginal } from 'rebass/styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import styled, { DefaultTheme, useTheme } from 'styled-components/macro'
|
||||
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
@@ -52,8 +51,11 @@ export const BaseButton = styled(RebassButton)<
|
||||
`
|
||||
|
||||
export const ButtonPrimary = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.deprecated_primary1};
|
||||
color: white;
|
||||
background-color: ${({ theme }) => theme.accentAction};
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
padding: 16px;
|
||||
color: ${({ theme }) => theme.accentTextLightPrimary};
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.deprecated_primary1)};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.deprecated_primary1)};
|
||||
@@ -78,26 +80,27 @@ export const ButtonPrimary = styled(BaseButton)`
|
||||
`
|
||||
|
||||
export const ButtonLight = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.deprecated_primary5};
|
||||
color: ${({ theme }) => theme.deprecated_primaryText1};
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
background-color: ${({ theme }) => theme.accentActionSoft};
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1pt ${({ theme, disabled }) => !disabled && darken(0.03, theme.deprecated_primary5)};
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.03, theme.deprecated_primary5)};
|
||||
box-shadow: 0 0 0 1pt ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
|
||||
background-color: ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
|
||||
}
|
||||
&:hover {
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.03, theme.deprecated_primary5)};
|
||||
background-color: ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
|
||||
}
|
||||
&:active {
|
||||
box-shadow: 0 0 0 1pt ${({ theme, disabled }) => !disabled && darken(0.05, theme.deprecated_primary5)};
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.deprecated_primary5)};
|
||||
box-shadow: 0 0 0 1pt ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
|
||||
background-color: ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
|
||||
}
|
||||
:disabled {
|
||||
opacity: 0.4;
|
||||
:hover {
|
||||
cursor: auto;
|
||||
background-color: ${({ theme }) => theme.deprecated_primary5};
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
border: 1px solid transparent;
|
||||
outline: none;
|
||||
@@ -167,22 +170,21 @@ export const ButtonOutlined = styled(BaseButton)`
|
||||
`
|
||||
|
||||
export const ButtonYellow = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.deprecated_yellow3};
|
||||
color: white;
|
||||
background-color: ${({ theme }) => theme.accentWarningSoft};
|
||||
color: ${({ theme }) => theme.accentWarning};
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.deprecated_yellow3)};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.deprecated_yellow3)};
|
||||
background-color: ${({ theme }) => theme.accentWarningSoft};
|
||||
}
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => darken(0.05, theme.deprecated_yellow3)};
|
||||
background: ${({ theme }) => theme.stateOverlayHover};
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
&:active {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.deprecated_yellow3)};
|
||||
background-color: ${({ theme }) => darken(0.1, theme.deprecated_yellow3)};
|
||||
background-color: ${({ theme }) => theme.accentWarningSoft};
|
||||
}
|
||||
&:disabled {
|
||||
background-color: ${({ theme }) => theme.deprecated_yellow3};
|
||||
opacity: 50%;
|
||||
background-color: ${({ theme }) => theme.accentWarningSoft};
|
||||
opacity: 60%;
|
||||
cursor: auto;
|
||||
}
|
||||
`
|
||||
@@ -343,23 +345,176 @@ export function ButtonRadioChecked({ active = false, children, ...rest }: { acti
|
||||
if (!active) {
|
||||
return (
|
||||
<ButtonOutlined $borderRadius="12px" padding="12px 8px" {...rest}>
|
||||
{<RowBetween>{children}</RowBetween>}
|
||||
<RowBetween>{children}</RowBetween>
|
||||
</ButtonOutlined>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<ActiveOutlined {...rest} padding="12px 8px" $borderRadius="12px">
|
||||
{
|
||||
<RowBetween>
|
||||
{children}
|
||||
<CheckboxWrapper>
|
||||
<Circle>
|
||||
<ResponsiveCheck size={13} stroke={theme.deprecated_white} />
|
||||
</Circle>
|
||||
</CheckboxWrapper>
|
||||
</RowBetween>
|
||||
}
|
||||
<RowBetween>
|
||||
{children}
|
||||
<CheckboxWrapper>
|
||||
<Circle>
|
||||
<ResponsiveCheck size={13} stroke={theme.deprecated_white} />
|
||||
</Circle>
|
||||
</CheckboxWrapper>
|
||||
</RowBetween>
|
||||
</ActiveOutlined>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const ButtonOverlay = styled.div`
|
||||
background-color: transparent;
|
||||
bottom: 0;
|
||||
border-radius: 16px;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transition: 150ms ease background-color;
|
||||
width: 100%;
|
||||
`
|
||||
export enum ButtonSize {
|
||||
small,
|
||||
medium,
|
||||
large,
|
||||
}
|
||||
export enum ButtonEmphasis {
|
||||
high,
|
||||
promotional,
|
||||
highSoft,
|
||||
medium,
|
||||
low,
|
||||
warning,
|
||||
destructive,
|
||||
}
|
||||
interface BaseButtonProps {
|
||||
size: ButtonSize
|
||||
emphasis: ButtonEmphasis
|
||||
}
|
||||
|
||||
function pickThemeButtonBackgroundColor({ theme, emphasis }: { theme: DefaultTheme; emphasis: ButtonEmphasis }) {
|
||||
switch (emphasis) {
|
||||
case ButtonEmphasis.high:
|
||||
return theme.accentAction
|
||||
case ButtonEmphasis.promotional:
|
||||
return theme.accentTextLightPrimary
|
||||
case ButtonEmphasis.highSoft:
|
||||
return theme.accentActionSoft
|
||||
case ButtonEmphasis.low:
|
||||
return 'transparent'
|
||||
case ButtonEmphasis.warning:
|
||||
return theme.accentWarningSoft
|
||||
case ButtonEmphasis.destructive:
|
||||
return theme.accentCritical
|
||||
case ButtonEmphasis.medium:
|
||||
default:
|
||||
return theme.backgroundInteractive
|
||||
}
|
||||
}
|
||||
function pickThemeButtonFontSize({ size }: { size: ButtonSize }) {
|
||||
switch (size) {
|
||||
case ButtonSize.large:
|
||||
return '20px'
|
||||
case ButtonSize.medium:
|
||||
return '16px'
|
||||
case ButtonSize.small:
|
||||
return '14px'
|
||||
default:
|
||||
return '16px'
|
||||
}
|
||||
}
|
||||
function pickThemeButtonLineHeight({ size }: { size: ButtonSize }) {
|
||||
switch (size) {
|
||||
case ButtonSize.large:
|
||||
return '24px'
|
||||
case ButtonSize.medium:
|
||||
return '20px'
|
||||
case ButtonSize.small:
|
||||
return '16px'
|
||||
default:
|
||||
return '20px'
|
||||
}
|
||||
}
|
||||
function pickThemeButtonPadding({ size }: { size: ButtonSize }) {
|
||||
switch (size) {
|
||||
case ButtonSize.large:
|
||||
return '16px'
|
||||
case ButtonSize.medium:
|
||||
return '10px 12px'
|
||||
case ButtonSize.small:
|
||||
return '8px'
|
||||
default:
|
||||
return '10px 12px'
|
||||
}
|
||||
}
|
||||
function pickThemeButtonTextColor({ theme, emphasis }: { theme: DefaultTheme; emphasis: ButtonEmphasis }) {
|
||||
switch (emphasis) {
|
||||
case ButtonEmphasis.high:
|
||||
case ButtonEmphasis.promotional:
|
||||
return theme.accentTextLightPrimary
|
||||
case ButtonEmphasis.highSoft:
|
||||
return theme.accentAction
|
||||
case ButtonEmphasis.low:
|
||||
return theme.textSecondary
|
||||
case ButtonEmphasis.warning:
|
||||
return theme.accentWarning
|
||||
case ButtonEmphasis.destructive:
|
||||
return theme.accentTextDarkPrimary
|
||||
case ButtonEmphasis.medium:
|
||||
default:
|
||||
return theme.textPrimary
|
||||
}
|
||||
}
|
||||
|
||||
const BaseThemeButton = styled.button<BaseButtonProps>`
|
||||
align-items: center;
|
||||
background-color: ${pickThemeButtonBackgroundColor};
|
||||
border-radius: 16px;
|
||||
border: 0;
|
||||
color: ${pickThemeButtonTextColor};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: ${pickThemeButtonFontSize};
|
||||
font-weight: 600;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
line-height: ${pickThemeButtonLineHeight};
|
||||
padding: ${pickThemeButtonPadding};
|
||||
position: relative;
|
||||
transition: 150ms ease opacity;
|
||||
|
||||
:active {
|
||||
${ButtonOverlay} {
|
||||
background-color: ${({ theme }) => theme.stateOverlayPressed};
|
||||
}
|
||||
}
|
||||
:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
}
|
||||
:focus {
|
||||
${ButtonOverlay} {
|
||||
background-color: ${({ theme }) => theme.stateOverlayPressed};
|
||||
}
|
||||
}
|
||||
:hover {
|
||||
${ButtonOverlay} {
|
||||
background-color: ${({ theme }) => theme.stateOverlayHover};
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
interface ThemeButtonProps extends React.ComponentPropsWithoutRef<'button'>, BaseButtonProps {}
|
||||
|
||||
export const ThemeButton = ({ children, ...rest }: ThemeButtonProps) => {
|
||||
return (
|
||||
<BaseThemeButton {...rest}>
|
||||
<ButtonOverlay />
|
||||
{children}
|
||||
</BaseThemeButton>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,15 +14,15 @@ export const LightCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg1};
|
||||
`
|
||||
|
||||
export const LightGreyCard = styled(Card)`
|
||||
export const LightGrayCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
`
|
||||
|
||||
export const GreyCard = styled(Card)`
|
||||
export const GrayCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg3};
|
||||
`
|
||||
|
||||
export const DarkGreyCard = styled(Card)`
|
||||
export const DarkGrayCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
`
|
||||
|
||||
|
||||
94
src/components/Charts/AnimatedInLineChart.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Group } from '@visx/group'
|
||||
import { LinePath } from '@visx/shape'
|
||||
import { easeCubicInOut } from 'd3'
|
||||
import React from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { animated, useSpring } from 'react-spring'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { LineChartProps } from './LineChart'
|
||||
|
||||
type AnimatedInLineChartProps<T> = Omit<LineChartProps<T>, 'height' | 'width' | 'children'>
|
||||
|
||||
const config = {
|
||||
duration: 800,
|
||||
easing: easeCubicInOut,
|
||||
}
|
||||
|
||||
// code reference: https://airbnb.io/visx/lineradial
|
||||
|
||||
function AnimatedInLineChart<T>({
|
||||
data,
|
||||
getX,
|
||||
getY,
|
||||
marginTop,
|
||||
curve,
|
||||
color,
|
||||
strokeWidth,
|
||||
}: AnimatedInLineChartProps<T>) {
|
||||
const lineRef = useRef<SVGPathElement>(null)
|
||||
const [lineLength, setLineLength] = useState(0)
|
||||
const [shouldAnimate, setShouldAnimate] = useState(false)
|
||||
const [hasAnimatedIn, setHasAnimatedIn] = useState(false)
|
||||
|
||||
const spring = useSpring({
|
||||
frame: shouldAnimate ? 0 : 1,
|
||||
config,
|
||||
onRest: () => {
|
||||
setShouldAnimate(false)
|
||||
setHasAnimatedIn(true)
|
||||
},
|
||||
})
|
||||
|
||||
// We need to check to see after the "invisble" line has been drawn
|
||||
// what the length is to be able to animate in the line for the first time
|
||||
// This will run on each render to see if there is a new line length
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => {
|
||||
if (lineRef.current) {
|
||||
const length = lineRef.current.getTotalLength()
|
||||
if (length !== lineLength) {
|
||||
setLineLength(length)
|
||||
}
|
||||
if (length > 0 && !shouldAnimate && !hasAnimatedIn) {
|
||||
setShouldAnimate(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
const theme = useTheme()
|
||||
const lineColor = color ?? theme.accentAction
|
||||
|
||||
return (
|
||||
<Group top={marginTop}>
|
||||
<LinePath curve={curve} x={getX} y={getY}>
|
||||
{({ path }) => {
|
||||
const d = path(data) || ''
|
||||
return (
|
||||
<>
|
||||
<animated.path
|
||||
d={d}
|
||||
ref={lineRef}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeOpacity={hasAnimatedIn ? 1 : 0}
|
||||
fill="none"
|
||||
stroke={lineColor}
|
||||
/>
|
||||
{shouldAnimate && lineLength !== 0 && (
|
||||
<animated.path
|
||||
d={d}
|
||||
strokeWidth={strokeWidth}
|
||||
fill="none"
|
||||
stroke={lineColor}
|
||||
strokeDashoffset={spring.frame.to((v) => v * lineLength)}
|
||||
strokeDasharray={lineLength}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</LinePath>
|
||||
</Group>
|
||||
)
|
||||
}
|
||||
|
||||
export default AnimatedInLineChart
|
||||
51
src/components/Charts/LineChart.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Group } from '@visx/group'
|
||||
import { LinePath } from '@visx/shape'
|
||||
import { CurveFactory } from 'd3'
|
||||
import React from 'react'
|
||||
import { ReactNode } from 'react'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
|
||||
export interface LineChartProps<T> {
|
||||
data: T[]
|
||||
getX: (t: T) => number
|
||||
getY: (t: T) => number
|
||||
marginTop?: number
|
||||
curve: CurveFactory
|
||||
color?: string
|
||||
strokeWidth: number
|
||||
children?: ReactNode
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
function LineChart<T>({
|
||||
data,
|
||||
getX,
|
||||
getY,
|
||||
marginTop,
|
||||
curve,
|
||||
color,
|
||||
strokeWidth,
|
||||
width,
|
||||
height,
|
||||
children,
|
||||
}: LineChartProps<T>) {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<svg width={width} height={height}>
|
||||
<Group top={marginTop}>
|
||||
<LinePath
|
||||
curve={curve}
|
||||
stroke={color ?? theme.accentAction}
|
||||
strokeWidth={strokeWidth}
|
||||
data={data}
|
||||
x={getX}
|
||||
y={getY}
|
||||
/>
|
||||
</Group>
|
||||
{children}
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(LineChart) as typeof LineChart
|
||||
@@ -1,291 +0,0 @@
|
||||
import { AxisBottom, TickFormatter } from '@visx/axis'
|
||||
import { localPoint } from '@visx/event'
|
||||
import { EventType } from '@visx/event/lib/types'
|
||||
import { GlyphCircle } from '@visx/glyph'
|
||||
import { Group } from '@visx/group'
|
||||
import { Line, LinePath } from '@visx/shape'
|
||||
import { bisect, curveBasis, NumberValue, scaleLinear } from 'd3'
|
||||
import { radius } from 'd3-curve-circlecorners'
|
||||
import { useActiveLocale } from 'hooks/useActiveLocale'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { TimePeriod } from 'hooks/useTopTokens'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { ArrowDownRight, ArrowUpRight } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import {
|
||||
dayHourFormatter,
|
||||
hourFormatter,
|
||||
monthDayFormatter,
|
||||
monthFormatter,
|
||||
monthYearDayFormatter,
|
||||
monthYearFormatter,
|
||||
weekFormatter,
|
||||
} from 'utils/formatChartTimes'
|
||||
|
||||
import data from './data.json'
|
||||
|
||||
const TIME_DISPLAYS: [TimePeriod, string][] = [
|
||||
[TimePeriod.hour, '1H'],
|
||||
[TimePeriod.day, '1D'],
|
||||
[TimePeriod.week, '1W'],
|
||||
[TimePeriod.month, '1M'],
|
||||
[TimePeriod.year, '1Y'],
|
||||
[TimePeriod.all, 'ALL'],
|
||||
]
|
||||
|
||||
type PricePoint = { value: number; timestamp: number }
|
||||
|
||||
function getPriceBounds(pricePoints: PricePoint[]): [number, number] {
|
||||
const prices = pricePoints.map((x) => x.value)
|
||||
const min = Math.min(...prices)
|
||||
const max = Math.max(...prices)
|
||||
return [min, max]
|
||||
}
|
||||
|
||||
const StyledUpArrow = styled(ArrowUpRight)`
|
||||
color: ${({ theme }) => theme.accentSuccess};
|
||||
`
|
||||
const StyledDownArrow = styled(ArrowDownRight)`
|
||||
color: ${({ theme }) => theme.accentFailure};
|
||||
`
|
||||
|
||||
function getDelta(start: number, current: number) {
|
||||
const delta = (current / start - 1) * 100
|
||||
const isPositive = Math.sign(delta) > 0
|
||||
|
||||
const formattedDelta = delta.toFixed(2) + '%'
|
||||
if (isPositive) {
|
||||
return ['+' + formattedDelta, <StyledUpArrow size={16} key="arrow-up" />]
|
||||
} else if (delta === 0) {
|
||||
return [formattedDelta, null]
|
||||
}
|
||||
return [formattedDelta, <StyledDownArrow size={16} key="arrow-down" />]
|
||||
}
|
||||
|
||||
export const ChartWrapper = styled.div`
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
`
|
||||
|
||||
export const ChartHeader = styled.div`
|
||||
position: absolute;
|
||||
`
|
||||
|
||||
export const TokenPrice = styled.span`
|
||||
font-size: 36px;
|
||||
line-height: 44px;
|
||||
`
|
||||
export const DeltaContainer = styled.div`
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
const ArrowCell = styled.div`
|
||||
padding-left: 2px;
|
||||
display: flex;
|
||||
`
|
||||
export const TimeOptionsContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 4px;
|
||||
gap: 4px;
|
||||
`
|
||||
const TimeButton = styled.button<{ active: boolean }>`
|
||||
background-color: ${({ theme, active }) => (active ? theme.accentActive : 'transparent')};
|
||||
font-size: 14px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
|
||||
function getTicks(startTimestamp: number, endTimestamp: number, numTicks = 5) {
|
||||
return Array.from(
|
||||
{ length: numTicks },
|
||||
(v, i) => endTimestamp - ((endTimestamp - startTimestamp) / (numTicks + 1)) * (i + 1)
|
||||
)
|
||||
}
|
||||
|
||||
function tickFormat(
|
||||
startTimestamp: number,
|
||||
endTimestamp: number,
|
||||
activeTimePeriod: TimePeriod,
|
||||
locale: string
|
||||
): [TickFormatter<NumberValue>, (v: number) => string, number[]] {
|
||||
switch (activeTimePeriod) {
|
||||
case TimePeriod.hour:
|
||||
return [hourFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
case TimePeriod.day:
|
||||
return [hourFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
case TimePeriod.week:
|
||||
return [weekFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp, 6)]
|
||||
case TimePeriod.month:
|
||||
return [monthDayFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
case TimePeriod.year:
|
||||
return [monthFormatter(locale), monthYearDayFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
case TimePeriod.all:
|
||||
return [monthYearFormatter(locale), monthYearDayFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
}
|
||||
}
|
||||
|
||||
const margin = { top: 86, bottom: 32, crosshair: 72 }
|
||||
const timeOptionsHeight = 44
|
||||
const crosshairDateOverhang = 80
|
||||
|
||||
interface PriceChartProps {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export function PriceChart({ width, height }: PriceChartProps) {
|
||||
const [activeTimePeriod, setTimePeriod] = useState(TimePeriod.hour)
|
||||
const locale = useActiveLocale()
|
||||
const theme = useTheme()
|
||||
|
||||
/* TODO: Implement API calls & cache to use here */
|
||||
const pricePoints = data[activeTimePeriod]
|
||||
const startingPrice = pricePoints[0]
|
||||
const endingPrice = pricePoints[pricePoints.length - 1]
|
||||
const initialState = { pricePoint: endingPrice, xCoordinate: null }
|
||||
const [selected, setSelected] = useState<{ pricePoint: PricePoint; xCoordinate: number | null }>(initialState)
|
||||
|
||||
const graphWidth = width + crosshairDateOverhang
|
||||
const graphHeight = height - timeOptionsHeight
|
||||
const graphInnerHeight = graphHeight - margin.top - margin.bottom
|
||||
|
||||
// Defining scales
|
||||
// x scale
|
||||
const timeScale = scaleLinear().domain([startingPrice.timestamp, endingPrice.timestamp]).range([0, width])
|
||||
// y scale
|
||||
const rdScale = scaleLinear().domain(getPriceBounds(pricePoints)).range([graphInnerHeight, 0])
|
||||
|
||||
const handleHover = useCallback(
|
||||
(event: Element | EventType) => {
|
||||
const { x } = localPoint(event) || { x: 0 }
|
||||
const x0 = timeScale.invert(x) // get timestamp from the scale
|
||||
const index = bisect(
|
||||
pricePoints.map((x) => x.timestamp),
|
||||
x0,
|
||||
1
|
||||
)
|
||||
|
||||
const d0 = pricePoints[index - 1]
|
||||
const d1 = pricePoints[index]
|
||||
let pricePoint = d0
|
||||
|
||||
const hasPreviousData = d1 && d1.timestamp
|
||||
if (hasPreviousData) {
|
||||
pricePoint = x0.valueOf() - d0.timestamp.valueOf() > d1.timestamp.valueOf() - x0.valueOf() ? d1 : d0
|
||||
}
|
||||
|
||||
setSelected({ pricePoint, xCoordinate: timeScale(pricePoint.timestamp) })
|
||||
},
|
||||
[timeScale, pricePoints]
|
||||
)
|
||||
|
||||
const [tickFormatter, crosshairDateFormatter, ticks] = tickFormat(
|
||||
startingPrice.timestamp,
|
||||
endingPrice.timestamp,
|
||||
activeTimePeriod,
|
||||
locale
|
||||
)
|
||||
const [delta, arrow] = getDelta(startingPrice.value, selected.pricePoint.value)
|
||||
const crosshairEdgeMax = width * 0.97
|
||||
const crosshairAtEdge = !!selected.xCoordinate && selected.xCoordinate > crosshairEdgeMax
|
||||
|
||||
return (
|
||||
<ChartWrapper>
|
||||
<ChartHeader>
|
||||
<TokenPrice>${selected.pricePoint.value.toFixed(2)}</TokenPrice>
|
||||
<DeltaContainer>
|
||||
{delta}
|
||||
<ArrowCell>{arrow}</ArrowCell>
|
||||
</DeltaContainer>
|
||||
</ChartHeader>
|
||||
<svg width={graphWidth} height={graphHeight}>
|
||||
<AxisBottom
|
||||
scale={timeScale}
|
||||
stroke={theme.backgroundOutline}
|
||||
tickFormat={tickFormatter}
|
||||
tickStroke={theme.backgroundOutline}
|
||||
tickLength={4}
|
||||
tickTransform={'translate(0 -5)'}
|
||||
tickValues={ticks}
|
||||
top={graphHeight - 1}
|
||||
tickLabelProps={() => ({
|
||||
fill: theme.textSecondary,
|
||||
fontSize: 12,
|
||||
textAnchor: 'middle',
|
||||
transform: 'translate(0 -24)',
|
||||
})}
|
||||
/>
|
||||
{selected.xCoordinate !== null && (
|
||||
<g>
|
||||
<text
|
||||
x={selected.xCoordinate + (crosshairAtEdge ? -4 : 4)}
|
||||
y={margin.crosshair + 10}
|
||||
textAnchor={crosshairAtEdge ? 'end' : 'start'}
|
||||
fontSize={12}
|
||||
fill={theme.textSecondary}
|
||||
>
|
||||
{crosshairDateFormatter(selected.pricePoint.timestamp)}
|
||||
</text>
|
||||
<Line
|
||||
from={{ x: selected.xCoordinate, y: margin.crosshair }}
|
||||
to={{ x: selected.xCoordinate, y: graphHeight }}
|
||||
stroke={theme.backgroundOutline}
|
||||
strokeWidth={1}
|
||||
pointerEvents="none"
|
||||
strokeDasharray="4,4"
|
||||
/>
|
||||
</g>
|
||||
)}
|
||||
<Group top={margin.top}>
|
||||
<LinePath
|
||||
/* ALL chart renders poorly using circle corners; use d3 curve for ALL instead */
|
||||
curve={activeTimePeriod === TimePeriod.all ? curveBasis : radius(0.25)}
|
||||
stroke={theme.accentActive}
|
||||
strokeWidth={2}
|
||||
data={pricePoints}
|
||||
x={(d: PricePoint) => timeScale(d.timestamp) ?? 0}
|
||||
y={(d: PricePoint) => rdScale(d.value) ?? 0}
|
||||
/>
|
||||
{selected.xCoordinate !== null && (
|
||||
<g>
|
||||
<GlyphCircle
|
||||
left={selected.xCoordinate}
|
||||
top={rdScale(selected.pricePoint.value)}
|
||||
size={50}
|
||||
fill={theme.accentActive}
|
||||
stroke={theme.backgroundOutline}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</g>
|
||||
)}
|
||||
</Group>
|
||||
<rect
|
||||
x={0}
|
||||
y={0}
|
||||
width={width}
|
||||
height={graphHeight}
|
||||
fill={'transparent'}
|
||||
onTouchStart={handleHover}
|
||||
onTouchMove={handleHover}
|
||||
onMouseMove={handleHover}
|
||||
onMouseLeave={() => setSelected(initialState)}
|
||||
/>
|
||||
</svg>
|
||||
<TimeOptionsContainer>
|
||||
{TIME_DISPLAYS.map(([value, display]) => (
|
||||
<TimeButton key={display} active={activeTimePeriod === value} onClick={() => setTimePeriod(value)}>
|
||||
{display}
|
||||
</TimeButton>
|
||||
))}
|
||||
</TimeOptionsContainer>
|
||||
</ChartWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default PriceChart
|
||||
78
src/components/Charts/SparklineChart.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { SparkLineLoadingBubble } from 'components/Tokens/TokenTable/TokenRow'
|
||||
import { curveCardinal, scaleLinear } from 'd3'
|
||||
import { SparklineMap, TopToken } from 'graphql/data/TopTokens'
|
||||
import { PricePoint } from 'graphql/data/util'
|
||||
import { TimePeriod } from 'graphql/data/util'
|
||||
import { memo } from 'react'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { getPriceBounds } from '../Tokens/TokenDetails/PriceChart'
|
||||
import LineChart from './LineChart'
|
||||
|
||||
const LoadingContainer = styled.div`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
interface SparklineChartProps {
|
||||
width: number
|
||||
height: number
|
||||
tokenData: TopToken
|
||||
pricePercentChange: number | undefined | null
|
||||
timePeriod: TimePeriod
|
||||
sparklineMap: SparklineMap
|
||||
}
|
||||
|
||||
function _SparklineChart({
|
||||
width,
|
||||
height,
|
||||
tokenData,
|
||||
pricePercentChange,
|
||||
timePeriod,
|
||||
sparklineMap,
|
||||
}: SparklineChartProps) {
|
||||
const theme = useTheme()
|
||||
// for sparkline
|
||||
const pricePoints = tokenData?.address ? sparklineMap[tokenData.address] : null
|
||||
|
||||
// Don't display if there's one or less pricepoints
|
||||
if (!pricePoints || pricePoints.length <= 1) {
|
||||
return (
|
||||
<LoadingContainer>
|
||||
<SparkLineLoadingBubble />
|
||||
</LoadingContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const startingPrice = pricePoints[0]
|
||||
const endingPrice = pricePoints[pricePoints.length - 1]
|
||||
const widthScale = scaleLinear()
|
||||
.domain(
|
||||
// the range of possible input values
|
||||
[startingPrice.timestamp, endingPrice.timestamp]
|
||||
)
|
||||
.range(
|
||||
// the range of possible output values that the inputs should be transformed to (see https://www.d3indepth.com/scales/ for details)
|
||||
[0, 110]
|
||||
)
|
||||
const rdScale = scaleLinear().domain(getPriceBounds(pricePoints)).range([30, 0])
|
||||
const curveTension = 0.9
|
||||
|
||||
return (
|
||||
<LineChart
|
||||
data={pricePoints}
|
||||
getX={(p: PricePoint) => widthScale(p.timestamp)}
|
||||
getY={(p: PricePoint) => rdScale(p.value)}
|
||||
curve={curveCardinal.tension(curveTension)}
|
||||
marginTop={5}
|
||||
color={pricePercentChange && pricePercentChange < 0 ? theme.accentFailure : theme.accentSuccess}
|
||||
strokeWidth={1.5}
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(_SparklineChart)
|
||||
47
src/components/Common/index.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { css } from 'styled-components/macro'
|
||||
|
||||
export const ScrollBarStyles = css<{ $isHorizontalScroll?: boolean }>`
|
||||
// Firefox scrollbar styling
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: ${({ theme }) => `${theme.backgroundOutline} transparent`};
|
||||
height: 100%;
|
||||
|
||||
// safari and chrome scrollbar styling
|
||||
::-webkit-scrollbar {
|
||||
background: transparent;
|
||||
|
||||
// Set height for horizontal scrolls
|
||||
${({ $isHorizontalScroll }) => {
|
||||
return $isHorizontalScroll
|
||||
? css`
|
||||
height: 4px;
|
||||
overflow-x: scroll;
|
||||
`
|
||||
: css`
|
||||
width: 4px;
|
||||
overflow-y: scroll;
|
||||
`
|
||||
}}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: ${({ theme }) => theme.backgroundOutline};
|
||||
border-radius: 8px;
|
||||
}
|
||||
`
|
||||
|
||||
export const OpacityHoverState = css`
|
||||
&:hover {
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: ${({ theme }) => theme.opacity.click};
|
||||
}
|
||||
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `opacity ${duration.medium} ${timing.ease}`};
|
||||
`
|
||||
@@ -1,22 +1,17 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import CopyHelper from 'components/AccountDetails/Copy'
|
||||
import Column from 'components/Column'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { AlertOctagon } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { BlockedIcon } from 'components/TokenSafety/TokenSafetyIcon'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
|
||||
import { CopyHelper } from '../../theme'
|
||||
import Modal from '../Modal'
|
||||
|
||||
const ContentWrapper = styled(Column)`
|
||||
align-items: center;
|
||||
margin: 32px;
|
||||
text-align: center;
|
||||
`
|
||||
const WarningIcon = styled(AlertOctagon)`
|
||||
min-height: 22px;
|
||||
min-width: 22px;
|
||||
color: ${({ theme }) => theme.deprecated_warning};
|
||||
font-size: 12px;
|
||||
`
|
||||
const Copy = styled(CopyHelper)`
|
||||
font-size: 12px;
|
||||
@@ -32,7 +27,7 @@ export default function ConnectedAccountBlocked(props: ConnectedAccountBlockedPr
|
||||
return (
|
||||
<Modal isOpen={props.isOpen} onDismiss={Function.prototype()}>
|
||||
<ContentWrapper>
|
||||
<WarningIcon />
|
||||
<BlockedIcon size="22px" />
|
||||
<ThemedText.DeprecatedLargeHeader lineHeight={2} marginBottom={1} marginTop={1}>
|
||||
<Trans>Blocked Address</Trans>
|
||||
</ThemedText.DeprecatedLargeHeader>
|
||||
@@ -49,7 +44,14 @@ export default function ConnectedAccountBlocked(props: ConnectedAccountBlockedPr
|
||||
<ThemedText.DeprecatedMain fontSize={12}>
|
||||
<Trans>If you believe this is an error, please send an email including your address to </Trans>{' '}
|
||||
</ThemedText.DeprecatedMain>
|
||||
<Copy iconSize={12} toCopy="compliance@uniswap.org" color={theme.deprecated_primary1} iconPosition="right">
|
||||
<Copy
|
||||
toCopy="compliance@uniswap.org"
|
||||
fontSize={14}
|
||||
iconSize={16}
|
||||
gap={6}
|
||||
color={theme.deprecated_primary1}
|
||||
iconPosition="right"
|
||||
>
|
||||
compliance@uniswap.org
|
||||
</Copy>
|
||||
</ContentWrapper>
|
||||
|
||||
@@ -2,10 +2,9 @@ import { Trans } from '@lingui/macro'
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
|
||||
import HoverInlineText from 'components/HoverInlineText'
|
||||
import { useMemo } from 'react'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
import { ThemedText } from '../../theme'
|
||||
import { warningSeverity } from '../../utils/prices'
|
||||
import { MouseoverTooltip } from '../Tooltip'
|
||||
@@ -31,18 +30,8 @@ export function FiatValue({
|
||||
const visibleDecimalPlaces = p < 1.05 ? 4 : 2
|
||||
|
||||
return (
|
||||
<ThemedText.DeprecatedBody fontSize={14} color={fiatValue ? theme.deprecated_text3 : theme.deprecated_text4}>
|
||||
{fiatValue ? (
|
||||
<Trans>
|
||||
$
|
||||
<HoverInlineText
|
||||
text={fiatValue?.toFixed(visibleDecimalPlaces, { groupSeparator: ',' })}
|
||||
textColor={fiatValue ? theme.deprecated_text3 : theme.deprecated_text4}
|
||||
/>
|
||||
</Trans>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
<ThemedText.DeprecatedBody fontSize={14} color={theme.textSecondary}>
|
||||
{fiatValue && <>${fiatValue?.toFixed(visibleDecimalPlaces, { groupSeparator: ',' })}</>}
|
||||
{priceImpact ? (
|
||||
<span style={{ color: priceImpactColor }}>
|
||||
{' '}
|
||||
|
||||
360
src/components/CurrencyInputPanel/SwapCurrencyInputPanel.tsx
Normal file
@@ -0,0 +1,360 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, ElementName, EventName } from '@uniswap/analytics-events'
|
||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
import { darken } from 'polished'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { Lock } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles'
|
||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
|
||||
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
|
||||
import { useCurrencyBalance } from '../../state/connection/hooks'
|
||||
import { ThemedText } from '../../theme'
|
||||
import { ButtonGray } from '../Button'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { Input as NumericalInput } from '../NumericalInput'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
|
||||
import { FiatValue } from './FiatValue'
|
||||
|
||||
const InputPanel = styled.div<{ hideInput?: boolean }>`
|
||||
${flexColumnNoWrap};
|
||||
position: relative;
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
|
||||
z-index: 1;
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
transition: height 1s ease;
|
||||
will-change: height;
|
||||
`
|
||||
|
||||
const FixedContainer = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
`
|
||||
|
||||
const Container = styled.div<{ hideInput: boolean }>`
|
||||
min-height: 44px;
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
`
|
||||
|
||||
const CurrencySelect = styled(ButtonGray)<{
|
||||
visible: boolean
|
||||
selected: boolean
|
||||
hideInput?: boolean
|
||||
disabled?: boolean
|
||||
}>`
|
||||
align-items: center;
|
||||
background-color: ${({ selected, theme }) => (selected ? theme.backgroundInteractive : theme.accentAction)};
|
||||
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
|
||||
box-shadow: ${({ selected }) => (selected ? 'none' : '0px 6px 10px rgba(0, 0, 0, 0.075)')};
|
||||
color: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
|
||||
cursor: pointer;
|
||||
height: unset;
|
||||
border-radius: 16px;
|
||||
outline: none;
|
||||
user-select: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
padding: ${({ selected }) => (selected ? '4px 8px 4px 4px' : '6px 6px 6px 8px')};
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
margin-left: ${({ hideInput }) => (hideInput ? '0' : '12px')};
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: ${({ theme, selected }) => (selected ? theme.backgroundInteractive : theme.accentAction)};
|
||||
}
|
||||
|
||||
&:before {
|
||||
background-size: 100%;
|
||||
border-radius: inherit;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: '';
|
||||
}
|
||||
|
||||
&:hover:before {
|
||||
background-color: ${({ theme }) => theme.stateOverlayHover};
|
||||
}
|
||||
|
||||
&:active:before {
|
||||
background-color: ${({ theme }) => theme.stateOverlayPressed};
|
||||
}
|
||||
|
||||
visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')};
|
||||
`
|
||||
|
||||
const InputRow = styled.div`
|
||||
${flexRowNoWrap};
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
const LabelRow = styled.div`
|
||||
${flexRowNoWrap};
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
|
||||
span:hover {
|
||||
cursor: pointer;
|
||||
color: ${({ theme }) => darken(0.2, theme.deprecated_text2)};
|
||||
}
|
||||
`
|
||||
|
||||
const FiatRow = styled(LabelRow)`
|
||||
justify-content: flex-end;
|
||||
min-height: 20px;
|
||||
padding: 8px 0px 0px 0px;
|
||||
`
|
||||
|
||||
const Aligner = styled.span`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
|
||||
margin: 0 0.25rem 0 0.35rem;
|
||||
height: 35%;
|
||||
margin-left: 8px;
|
||||
|
||||
path {
|
||||
stroke: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
|
||||
stroke-width: 2px;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledTokenName = styled.span<{ active?: boolean }>`
|
||||
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.25rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
`
|
||||
|
||||
const StyledBalanceMax = styled.button<{ disabled?: boolean }>`
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
|
||||
padding: 4px 6px;
|
||||
pointer-events: ${({ disabled }) => (!disabled ? 'initial' : 'none')};
|
||||
|
||||
:hover {
|
||||
opacity: ${({ disabled }) => (!disabled ? 0.8 : 0.4)};
|
||||
}
|
||||
|
||||
:focus {
|
||||
outline: none;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean }>`
|
||||
${loadingOpacityMixin};
|
||||
text-align: left;
|
||||
font-size: 36px;
|
||||
line-height: 44px;
|
||||
font-variant: small-caps;
|
||||
`
|
||||
|
||||
interface SwapCurrencyInputPanelProps {
|
||||
value: string
|
||||
onUserInput: (value: string) => void
|
||||
onMax?: () => void
|
||||
showMaxButton: boolean
|
||||
label?: ReactNode
|
||||
onCurrencySelect?: (currency: Currency) => void
|
||||
currency?: Currency | null
|
||||
hideBalance?: boolean
|
||||
pair?: Pair | null
|
||||
hideInput?: boolean
|
||||
otherCurrency?: Currency | null
|
||||
fiatValue?: CurrencyAmount<Token> | null
|
||||
priceImpact?: Percent
|
||||
id: string
|
||||
showCommonBases?: boolean
|
||||
showCurrencyAmount?: boolean
|
||||
disableNonToken?: boolean
|
||||
renderBalance?: (amount: CurrencyAmount<Currency>) => ReactNode
|
||||
locked?: boolean
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
export default function SwapCurrencyInputPanel({
|
||||
value,
|
||||
onUserInput,
|
||||
onMax,
|
||||
showMaxButton,
|
||||
onCurrencySelect,
|
||||
currency,
|
||||
otherCurrency,
|
||||
id,
|
||||
showCommonBases,
|
||||
showCurrencyAmount,
|
||||
disableNonToken,
|
||||
renderBalance,
|
||||
fiatValue,
|
||||
priceImpact,
|
||||
hideBalance = false,
|
||||
pair = null, // used for double token logo
|
||||
hideInput = false,
|
||||
locked = false,
|
||||
loading = false,
|
||||
...rest
|
||||
}: SwapCurrencyInputPanelProps) {
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const { account, chainId } = useWeb3React()
|
||||
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
|
||||
const theme = useTheme()
|
||||
|
||||
const handleDismissSearch = useCallback(() => {
|
||||
setModalOpen(false)
|
||||
}, [setModalOpen])
|
||||
|
||||
const chainAllowed = isSupportedChain(chainId)
|
||||
|
||||
return (
|
||||
<InputPanel id={id} hideInput={hideInput} {...rest}>
|
||||
{locked && (
|
||||
<FixedContainer>
|
||||
<AutoColumn gap="sm" justify="center">
|
||||
<Lock />
|
||||
<ThemedText.DeprecatedLabel fontSize="12px" textAlign="center" padding="0 12px">
|
||||
<Trans>The market price is outside your specified price range. Single-asset deposit only.</Trans>
|
||||
</ThemedText.DeprecatedLabel>
|
||||
</AutoColumn>
|
||||
</FixedContainer>
|
||||
)}
|
||||
<Container hideInput={hideInput}>
|
||||
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}}>
|
||||
{!hideInput && (
|
||||
<StyledNumericalInput
|
||||
className="token-amount-input"
|
||||
value={value}
|
||||
onUserInput={onUserInput}
|
||||
disabled={!chainAllowed}
|
||||
$loading={loading}
|
||||
/>
|
||||
)}
|
||||
|
||||
<CurrencySelect
|
||||
disabled={!chainAllowed}
|
||||
visible={currency !== undefined}
|
||||
selected={!!currency}
|
||||
hideInput={hideInput}
|
||||
className="open-currency-select-button"
|
||||
onClick={() => {
|
||||
if (onCurrencySelect) {
|
||||
setModalOpen(true)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Aligner>
|
||||
<RowFixed>
|
||||
{pair ? (
|
||||
<span style={{ marginRight: '0.5rem' }}>
|
||||
<DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
|
||||
</span>
|
||||
) : currency ? (
|
||||
<CurrencyLogo style={{ marginRight: '2px' }} currency={currency} size="24px" />
|
||||
) : null}
|
||||
{pair ? (
|
||||
<StyledTokenName className="pair-name-container">
|
||||
{pair?.token0.symbol}:{pair?.token1.symbol}
|
||||
</StyledTokenName>
|
||||
) : (
|
||||
<StyledTokenName className="token-symbol-container" active={Boolean(currency && currency.symbol)}>
|
||||
{(currency && currency.symbol && currency.symbol.length > 20
|
||||
? currency.symbol.slice(0, 4) +
|
||||
'...' +
|
||||
currency.symbol.slice(currency.symbol.length - 5, currency.symbol.length)
|
||||
: currency?.symbol) || <Trans>Select token</Trans>}
|
||||
</StyledTokenName>
|
||||
)}
|
||||
</RowFixed>
|
||||
{onCurrencySelect && <StyledDropDown selected={!!currency} />}
|
||||
</Aligner>
|
||||
</CurrencySelect>
|
||||
</InputRow>
|
||||
{Boolean(!hideInput && !hideBalance) && (
|
||||
<FiatRow>
|
||||
<RowBetween>
|
||||
<LoadingOpacityContainer $loading={loading}>
|
||||
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />
|
||||
</LoadingOpacityContainer>
|
||||
{account ? (
|
||||
<RowFixed style={{ height: '17px' }}>
|
||||
<ThemedText.DeprecatedBody
|
||||
color={theme.textSecondary}
|
||||
fontWeight={400}
|
||||
fontSize={14}
|
||||
style={{ display: 'inline' }}
|
||||
>
|
||||
{!hideBalance && currency && selectedCurrencyBalance ? (
|
||||
renderBalance ? (
|
||||
renderBalance(selectedCurrencyBalance)
|
||||
) : (
|
||||
<Trans>Balance: {formatCurrencyAmount(selectedCurrencyBalance, 4)}</Trans>
|
||||
)
|
||||
) : null}
|
||||
</ThemedText.DeprecatedBody>
|
||||
{showMaxButton && selectedCurrencyBalance ? (
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={EventName.SWAP_MAX_TOKEN_AMOUNT_SELECTED}
|
||||
element={ElementName.MAX_TOKEN_AMOUNT_BUTTON}
|
||||
>
|
||||
<StyledBalanceMax onClick={onMax}>
|
||||
<Trans>Max</Trans>
|
||||
</StyledBalanceMax>
|
||||
</TraceEvent>
|
||||
) : null}
|
||||
</RowFixed>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
</RowBetween>
|
||||
</FiatRow>
|
||||
)}
|
||||
</Container>
|
||||
{onCurrencySelect && (
|
||||
<CurrencySearchModal
|
||||
isOpen={modalOpen}
|
||||
onDismiss={handleDismissSearch}
|
||||
onCurrencySelect={onCurrencySelect}
|
||||
selectedCurrency={currency}
|
||||
otherSelectedCurrency={otherCurrency}
|
||||
showCommonBases={showCommonBases}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
disableNonToken={disableNonToken}
|
||||
/>
|
||||
)}
|
||||
</InputPanel>
|
||||
)
|
||||
}
|
||||
@@ -1,32 +1,32 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, ElementName, EventName } from '@uniswap/analytics-events'
|
||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
import { darken } from 'polished'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { Lock } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles'
|
||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
|
||||
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
import { useCurrencyBalance } from '../../state/connection/hooks'
|
||||
import { ThemedText } from '../../theme'
|
||||
import { ButtonGray } from '../Button'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import CurrencyLogo from '../Logo/CurrencyLogo'
|
||||
import { Input as NumericalInput } from '../NumericalInput'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
|
||||
import { FiatValue } from './FiatValue'
|
||||
|
||||
const InputPanel = styled.div<{ hideInput?: boolean }>`
|
||||
${({ theme }) => theme.flexColumnNoWrap}
|
||||
${flexColumnNoWrap};
|
||||
position: relative;
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
|
||||
background-color: ${({ theme, hideInput }) => (hideInput ? 'transparent' : theme.deprecated_bg2)};
|
||||
@@ -96,14 +96,14 @@ const CurrencySelect = styled(ButtonGray)<{
|
||||
`
|
||||
|
||||
const InputRow = styled.div<{ selected: boolean }>`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
${flexRowNoWrap};
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: ${({ selected }) => (selected ? ' 1rem 1rem 0.75rem 1rem' : '1rem 1rem 1rem 1rem')};
|
||||
`
|
||||
|
||||
const LabelRow = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
${flexRowNoWrap};
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
font-size: 0.75rem;
|
||||
@@ -117,7 +117,8 @@ const LabelRow = styled.div`
|
||||
|
||||
const FiatRow = styled(LabelRow)`
|
||||
justify-content: flex-end;
|
||||
height: 16px;
|
||||
padding: 0px 1rem 0.75rem;
|
||||
height: 32px;
|
||||
`
|
||||
|
||||
const Aligner = styled.span`
|
||||
@@ -139,7 +140,7 @@ const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
|
||||
|
||||
const StyledTokenName = styled.span<{ active?: boolean }>`
|
||||
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.25rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
|
||||
font-size: ${({ active }) => (active ? '18px' : '18px')};
|
||||
font-size: 20px;
|
||||
`
|
||||
|
||||
const StyledBalanceMax = styled.button<{ disabled?: boolean }>`
|
||||
@@ -269,7 +270,7 @@ export default function CurrencyInputPanel({
|
||||
<DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
|
||||
</span>
|
||||
) : currency ? (
|
||||
<CurrencyLogo style={{ marginRight: '0.5rem' }} currency={currency} size={'24px'} />
|
||||
<CurrencyLogo style={{ marginRight: '0.5rem' }} currency={currency} size="24px" />
|
||||
) : null}
|
||||
{pair ? (
|
||||
<StyledTokenName className="pair-name-container">
|
||||
@@ -314,7 +315,7 @@ export default function CurrencyInputPanel({
|
||||
</ThemedText.DeprecatedBody>
|
||||
{showMaxButton && selectedCurrencyBalance ? (
|
||||
<TraceEvent
|
||||
events={[Event.onClick]}
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={EventName.SWAP_MAX_TOKEN_AMOUNT_SELECTED}
|
||||
element={ElementName.MAX_TOKEN_AMOUNT_BUTTON}
|
||||
>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import Logo from '../Logo'
|
||||
|
||||
const StyledLogo = styled(Logo)<{ size: string }>`
|
||||
width: ${({ size }) => size};
|
||||
height: ${({ size }) => size};
|
||||
background: radial-gradient(white 50%, #ffffff00 calc(75% + 1px), #ffffff00 100%);
|
||||
border-radius: 50%;
|
||||
-mox-box-shadow: 0 0 1px black;
|
||||
-webkit-box-shadow: 0 0 1px black;
|
||||
box-shadow: 0 0 1px black;
|
||||
border: 0px solid rgba(255, 255, 255, 0);
|
||||
`
|
||||
|
||||
const StyledNativeLogo = styled(StyledLogo)`
|
||||
-mox-box-shadow: 0 0 1px white;
|
||||
-webkit-box-shadow: 0 0 1px white;
|
||||
box-shadow: 0 0 1px white;
|
||||
`
|
||||
|
||||
export default function CurrencyLogo({
|
||||
currency,
|
||||
size = '24px',
|
||||
style,
|
||||
...rest
|
||||
}: {
|
||||
currency?: Currency | null
|
||||
size?: string
|
||||
style?: React.CSSProperties
|
||||
}) {
|
||||
const props = {
|
||||
alt: `${currency?.symbol ?? 'token'} logo`,
|
||||
size,
|
||||
srcs: useCurrencyLogoURIs(currency),
|
||||
style,
|
||||
...rest,
|
||||
}
|
||||
|
||||
return currency?.isNative ? <StyledNativeLogo {...props} /> : <StyledLogo {...props} />
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import CurrencyLogo from '../Logo/CurrencyLogo'
|
||||
|
||||
const Wrapper = styled.div<{ margin: boolean; sizeraw: number }>`
|
||||
position: relative;
|
||||
@@ -18,7 +18,7 @@ interface DoubleCurrencyLogoProps {
|
||||
}
|
||||
|
||||
const HigherLogo = styled(CurrencyLogo)`
|
||||
z-index: 2;
|
||||
z-index: 1;
|
||||
`
|
||||
const CoveredLogo = styled(CurrencyLogo)<{ sizeraw: number }>`
|
||||
position: absolute;
|
||||
|
||||
@@ -49,7 +49,7 @@ export default function DowntimeWarning() {
|
||||
|
||||
switch (chainId) {
|
||||
case SupportedChainId.OPTIMISM:
|
||||
case SupportedChainId.OPTIMISTIC_KOVAN:
|
||||
case SupportedChainId.OPTIMISM_GOERLI:
|
||||
return (
|
||||
<Wrapper>
|
||||
<Trans>
|
||||
|
||||
@@ -20,7 +20,6 @@ const FallbackWrapper = styled.div`
|
||||
const BodyWrapper = styled.div<{ margin?: string }>`
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
white-space: ;
|
||||
`
|
||||
|
||||
const CodeBlockWrapper = styled.div`
|
||||
@@ -99,7 +98,7 @@ export default class ErrorBoundary extends React.Component<PropsWithChildren<unk
|
||||
return (
|
||||
<FallbackWrapper>
|
||||
<BodyWrapper>
|
||||
<AutoColumn gap={'md'}>
|
||||
<AutoColumn gap="md">
|
||||
<SomethingWentWrongWrapper>
|
||||
<ThemedText.DeprecatedLabel fontSize={24} fontWeight={600}>
|
||||
<Trans>Something went wrong</Trans>
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getChainInfoOrDefault } from 'constants/chainInfo'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
import { useNetworkTokenBalances } from 'hooks/useNetworkTokenBalances'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import NetworkBalance from './NetworkBalance'
|
||||
|
||||
const BalancesCard = styled.div`
|
||||
width: 284px;
|
||||
height: fit-content;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
padding: 20px;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border-radius: 12px;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
`
|
||||
const ErrorState = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
`
|
||||
const ErrorText = styled.span`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
const NetworkBalancesSection = styled.div`
|
||||
height: fit-content;
|
||||
`
|
||||
const TotalBalanceSection = styled.div`
|
||||
height: fit-content;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 20px;
|
||||
`
|
||||
const TotalBalance = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
margin-top: 12px;
|
||||
align-items: center;
|
||||
`
|
||||
const TotalBalanceItem = styled.div`
|
||||
display: flex;
|
||||
`
|
||||
|
||||
export default function BalanceSummary({
|
||||
address,
|
||||
networkBalances,
|
||||
totalBalance,
|
||||
}: {
|
||||
address: string
|
||||
networkBalances: (JSX.Element | null)[] | null
|
||||
totalBalance: number
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
const tokenSymbol = useToken(address)?.symbol
|
||||
const { loading, error, data } = useNetworkTokenBalances({ address })
|
||||
|
||||
const { chainId: connectedChainId } = useWeb3React()
|
||||
|
||||
const { label: connectedLabel, logoUrl: connectedLogoUrl } = getChainInfoOrDefault(connectedChainId)
|
||||
const connectedFiatValue = 1
|
||||
const multipleBalances = true // for testing purposes
|
||||
|
||||
if (loading) return null
|
||||
return (
|
||||
<BalancesCard>
|
||||
{error ? (
|
||||
<ErrorState>
|
||||
<AlertTriangle size={24} />
|
||||
<ErrorText>
|
||||
<Trans>There was an error loading your {tokenSymbol} balance</Trans>
|
||||
</ErrorText>
|
||||
</ErrorState>
|
||||
) : multipleBalances ? (
|
||||
<>
|
||||
<TotalBalanceSection>
|
||||
Your balance across all networks
|
||||
<TotalBalance>
|
||||
<TotalBalanceItem>{`${totalBalance} ${tokenSymbol}`}</TotalBalanceItem>
|
||||
<TotalBalanceItem>$4,210.12</TotalBalanceItem>
|
||||
</TotalBalance>
|
||||
</TotalBalanceSection>
|
||||
<NetworkBalancesSection>Your balances by network</NetworkBalancesSection>
|
||||
{data && networkBalances}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Your balance on {connectedLabel}
|
||||
<NetworkBalance
|
||||
logoUrl={connectedLogoUrl}
|
||||
balance={'1'}
|
||||
tokenSymbol={tokenSymbol ?? 'XXX'}
|
||||
fiatValue={connectedFiatValue}
|
||||
label={connectedLabel}
|
||||
networkColor={theme.textPrimary}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</BalancesCard>
|
||||
)
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
import { useNetworkTokenBalances } from 'hooks/useNetworkTokenBalances'
|
||||
import { useState } from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { SMALLEST_MOBILE_MEDIA_BREAKPOINT } from '../constants'
|
||||
import { LoadingBubble } from '../loading'
|
||||
|
||||
const PLACEHOLDER_NAV_FOOTER_HEIGHT = '56px'
|
||||
const BalanceFooter = styled.div`
|
||||
height: fit-content;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border-radius: 20px 20px 0px 0px;
|
||||
padding: 12px 16px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
width: 100%;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: ${PLACEHOLDER_NAV_FOOTER_HEIGHT};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-content: center;
|
||||
`
|
||||
const BalanceValue = styled.div`
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
`
|
||||
const BalanceTotal = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
const BalanceInfo = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-direction: column;
|
||||
`
|
||||
const FakeFooterNavBar = styled.div`
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
background-color: ${({ theme }) => theme.backgroundBackdrop};
|
||||
height: ${PLACEHOLDER_NAV_FOOTER_HEIGHT};
|
||||
width: 100%;
|
||||
align-items: flex-end;
|
||||
padding: 20px 8px;
|
||||
font-size: 10px;
|
||||
`
|
||||
const FiatValue = styled.span`
|
||||
display: flex;
|
||||
align-self: flex-end;
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
|
||||
@media only screen and (max-width: ${SMALLEST_MOBILE_MEDIA_BREAKPOINT}) {
|
||||
line-height: 16px;
|
||||
}
|
||||
`
|
||||
const NetworkBalancesSection = styled.div`
|
||||
height: fit-content;
|
||||
border-top: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 16px 0px 8px 0px;
|
||||
margin-top: 16px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
const NetworkBalancesLabel = styled.span`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
const SwapButton = styled.button`
|
||||
background-color: ${({ theme }) => theme.accentAction};
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
padding: 12px 16px;
|
||||
width: 120px;
|
||||
height: 44px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
justify-content: center;
|
||||
`
|
||||
const TotalBalancesSection = styled.div`
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`
|
||||
const ViewAll = styled.span`
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
`
|
||||
const ErrorState = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding-right: 8px;
|
||||
`
|
||||
const LoadingState = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
`
|
||||
const TopBalanceLoadBubble = styled(LoadingBubble)`
|
||||
height: 12px;
|
||||
width: 172px;
|
||||
`
|
||||
const BottomBalanceLoadBubble = styled(LoadingBubble)`
|
||||
height: 16px;
|
||||
width: 188px;
|
||||
`
|
||||
const ErrorText = styled.span`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
|
||||
export default function FooterBalanceSummary({
|
||||
address,
|
||||
networkBalances,
|
||||
totalBalance,
|
||||
}: {
|
||||
address: string
|
||||
networkBalances: (JSX.Element | null)[] | null
|
||||
totalBalance: number
|
||||
}) {
|
||||
const tokenSymbol = useToken(address)?.symbol
|
||||
const [showMultipleBalances, setShowMultipleBalances] = useState(false)
|
||||
const multipleBalances = false // for testing purposes
|
||||
const networkNameIfOneBalance = 'Ethereum' // for testing purposes
|
||||
const { loading, error } = useNetworkTokenBalances({ address })
|
||||
return (
|
||||
<BalanceFooter>
|
||||
<TotalBalancesSection>
|
||||
{loading ? (
|
||||
<LoadingState>
|
||||
<TopBalanceLoadBubble></TopBalanceLoadBubble>
|
||||
<BottomBalanceLoadBubble></BottomBalanceLoadBubble>
|
||||
</LoadingState>
|
||||
) : error ? (
|
||||
<ErrorState>
|
||||
<AlertTriangle size={17} />
|
||||
<ErrorText>There was an error fetching your balance</ErrorText>
|
||||
</ErrorState>
|
||||
) : (
|
||||
<BalanceInfo>
|
||||
{multipleBalances ? 'Balance on all networks' : `Your balance on ${networkNameIfOneBalance}`}
|
||||
<BalanceTotal>
|
||||
<BalanceValue>
|
||||
{totalBalance} {tokenSymbol}
|
||||
</BalanceValue>
|
||||
<FiatValue>($107, 610.04)</FiatValue>
|
||||
</BalanceTotal>
|
||||
{multipleBalances && (
|
||||
<ViewAll onClick={() => setShowMultipleBalances(!showMultipleBalances)}>
|
||||
{showMultipleBalances ? 'Hide' : 'View'} all balances
|
||||
</ViewAll>
|
||||
)}
|
||||
</BalanceInfo>
|
||||
)}
|
||||
<SwapButton onClick={() => (window.location.href = 'https://app.uniswap.org/#/swap')}>Swap</SwapButton>
|
||||
</TotalBalancesSection>
|
||||
{showMultipleBalances && (
|
||||
<NetworkBalancesSection>
|
||||
<NetworkBalancesLabel>Your balances by network</NetworkBalancesLabel> {networkBalances}
|
||||
</NetworkBalancesSection>
|
||||
)}
|
||||
<FakeFooterNavBar>**leaving space for updated nav footer**</FakeFooterNavBar>
|
||||
</BalanceFooter>
|
||||
)
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { ChartWrapper, DeltaContainer, TokenPrice } from '../../Charts/PriceChart'
|
||||
import { LoadingBubble } from '../loading'
|
||||
import {
|
||||
AboutHeader,
|
||||
AboutSection,
|
||||
BreadcrumbNavLink,
|
||||
ChartContainer,
|
||||
ChartHeader,
|
||||
ContractAddressSection,
|
||||
ResourcesContainer,
|
||||
Stat,
|
||||
StatPair,
|
||||
StatsSection,
|
||||
TokenInfoContainer,
|
||||
TokenNameCell,
|
||||
TopArea,
|
||||
} from './TokenDetail'
|
||||
|
||||
/* Loading state bubbles */
|
||||
const LoadingDetailBubble = styled(LoadingBubble)`
|
||||
height: 16px;
|
||||
width: 180px;
|
||||
`
|
||||
const TitleLoadingBubble = styled(LoadingDetailBubble)`
|
||||
width: 140px;
|
||||
`
|
||||
const SquareLoadingBubble = styled(LoadingDetailBubble)`
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
margin-top: 4px;
|
||||
`
|
||||
const PriceLoadingBubble = styled(SquareLoadingBubble)`
|
||||
height: 40px;
|
||||
`
|
||||
const LongLoadingBubble = styled(LoadingDetailBubble)`
|
||||
width: 100%;
|
||||
`
|
||||
const HalfLoadingBubble = styled(LoadingDetailBubble)`
|
||||
width: 50%;
|
||||
`
|
||||
const IconLoadingBubble = styled(LoadingDetailBubble)`
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
`
|
||||
const StatLoadingBubble = styled(SquareLoadingBubble)`
|
||||
width: 116px;
|
||||
`
|
||||
const StatsLoadingContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
const ChartAnimation = styled.div`
|
||||
display: flex;
|
||||
animation: wave 8s cubic-bezier(0.36, 0.45, 0.63, 0.53) infinite;
|
||||
|
||||
@keyframes wave {
|
||||
0% {
|
||||
margin-left: 0;
|
||||
}
|
||||
100% {
|
||||
margin-left: -800px;
|
||||
}
|
||||
}
|
||||
`
|
||||
const Space = styled.div<{ heightSize: number }>`
|
||||
height: ${({ heightSize }) => `${heightSize}px`};
|
||||
`
|
||||
/* Loading State: row component with loading bubbles */
|
||||
export default function LoadingTokenDetail() {
|
||||
return (
|
||||
<TopArea>
|
||||
<BreadcrumbNavLink to="/explore">
|
||||
<Space heightSize={20} />
|
||||
</BreadcrumbNavLink>
|
||||
<ChartHeader>
|
||||
<TokenInfoContainer>
|
||||
<TokenNameCell>
|
||||
<IconLoadingBubble />
|
||||
<TitleLoadingBubble />
|
||||
</TokenNameCell>
|
||||
</TokenInfoContainer>
|
||||
<ChartContainer>
|
||||
<ChartWrapper>
|
||||
<ChartHeader>
|
||||
<TokenPrice>
|
||||
<PriceLoadingBubble />
|
||||
</TokenPrice>
|
||||
<DeltaContainer>
|
||||
<Space heightSize={20} />
|
||||
</DeltaContainer>
|
||||
</ChartHeader>
|
||||
<ChartAnimation>
|
||||
<svg width="416" height="160" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M 0 80 Q 104 10, 208 80 T 416 80" stroke="#2e3138" fill="transparent" strokeWidth="2" />
|
||||
</svg>
|
||||
<svg width="416" height="160" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M 0 80 Q 104 10, 208 80 T 416 80" stroke="#2e3138" fill="transparent" strokeWidth="2" />
|
||||
</svg>
|
||||
<svg width="416" height="160" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M 0 80 Q 104 10, 208 80 T 416 80" stroke="#2e3138" fill="transparent" strokeWidth="2" />
|
||||
</svg>
|
||||
<svg width="416" height="160" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M 0 80 Q 104 10, 208 80 T 416 80" stroke="#2e3138" fill="transparent" strokeWidth="2" />
|
||||
</svg>
|
||||
<svg width="416" height="160" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M 0 80 Q 104 10, 208 80 T 416 80" stroke="#2e3138" fill="transparent" strokeWidth="2" />
|
||||
</svg>
|
||||
</ChartAnimation>
|
||||
</ChartWrapper>
|
||||
</ChartContainer>
|
||||
<Space heightSize={32} />
|
||||
</ChartHeader>
|
||||
<AboutSection>
|
||||
<AboutHeader>
|
||||
<SquareLoadingBubble />
|
||||
</AboutHeader>
|
||||
<LongLoadingBubble />
|
||||
<LongLoadingBubble />
|
||||
<HalfLoadingBubble />
|
||||
|
||||
<ResourcesContainer>{null}</ResourcesContainer>
|
||||
</AboutSection>
|
||||
<StatsSection>
|
||||
<StatsLoadingContainer>
|
||||
<StatPair>
|
||||
<Stat>
|
||||
<HalfLoadingBubble />
|
||||
<StatLoadingBubble />
|
||||
</Stat>
|
||||
<Stat>
|
||||
<HalfLoadingBubble />
|
||||
<StatLoadingBubble />
|
||||
</Stat>
|
||||
</StatPair>
|
||||
<StatPair>
|
||||
<Stat>
|
||||
<HalfLoadingBubble />
|
||||
<StatLoadingBubble />
|
||||
</Stat>
|
||||
<Stat>
|
||||
<HalfLoadingBubble />
|
||||
<StatLoadingBubble />
|
||||
</Stat>
|
||||
</StatPair>
|
||||
</StatsLoadingContainer>
|
||||
</StatsSection>
|
||||
<ContractAddressSection>{null}</ContractAddressSection>
|
||||
</TopArea>
|
||||
)
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const Balance = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
`
|
||||
const BalanceItem = styled.div`
|
||||
display: flex;
|
||||
`
|
||||
const BalanceRow = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
`
|
||||
const Logo = styled.img`
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
margin-right: 8px;
|
||||
`
|
||||
const Network = styled.span<{ color: string }>`
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
font-weight: 500;
|
||||
color: ${({ color }) => color};
|
||||
`
|
||||
const NetworkBalanceContainer = styled.div`
|
||||
display: flex;
|
||||
padding-top: 16px;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
export default function NetworkBalance({
|
||||
logoUrl,
|
||||
balance,
|
||||
tokenSymbol,
|
||||
fiatValue,
|
||||
label,
|
||||
networkColor,
|
||||
}: {
|
||||
logoUrl: string
|
||||
balance: string
|
||||
tokenSymbol: string
|
||||
fiatValue: string | number
|
||||
label: string
|
||||
networkColor: string | undefined
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<NetworkBalanceContainer>
|
||||
<Logo src={logoUrl} />
|
||||
<Balance>
|
||||
<BalanceRow>
|
||||
<BalanceItem>
|
||||
{balance} {tokenSymbol}
|
||||
</BalanceItem>
|
||||
<BalanceItem>${fiatValue}</BalanceItem>
|
||||
</BalanceRow>
|
||||
<Network color={networkColor ?? theme.textPrimary}>{label}</Network>
|
||||
</Balance>
|
||||
</NetworkBalanceContainer>
|
||||
)
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { darken } from 'polished'
|
||||
import { useRef, useState } from 'react'
|
||||
import { Check, Link, Share, Twitter } from 'react-feather'
|
||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
const TWITTER_WIDTH = 560
|
||||
const TWITTER_HEIGHT = 480
|
||||
|
||||
const ShareButtonDisplay = styled.div`
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => darken(0.1, theme.textSecondary)};
|
||||
}
|
||||
`
|
||||
const ShareActions = styled.div`
|
||||
position: absolute;
|
||||
top: 28px;
|
||||
right: 0px;
|
||||
padding: 8px 0px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: fit-content;
|
||||
overflow: auto;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
box-shadow: ${({ theme }) => theme.flyoutDropShadow};
|
||||
border-radius: 12px;
|
||||
`
|
||||
const ShareAction = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
font-size: 16px;
|
||||
gap: 8px;
|
||||
width: 200px;
|
||||
height: 48px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.backgroundContainer};
|
||||
}
|
||||
`
|
||||
|
||||
const LinkCopied = styled.div<{ show: boolean }>`
|
||||
display: ${({ show }) => (show ? 'flex' : 'none')};
|
||||
width: 328px;
|
||||
height: 72px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
background-color: ${({ theme }) => theme.backgroundBackdrop};
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 24px 16px;
|
||||
position: absolute;
|
||||
right: 32px;
|
||||
bottom: 32px;
|
||||
font-size: 14px;
|
||||
gap: 8px;
|
||||
border: 1px solid rgba(153, 161, 189, 0.08);
|
||||
box-shadow: ${({ theme }) => theme.flyoutDropShadow};
|
||||
border-radius: 20px;
|
||||
animation: floatIn 0s ease-in 3s forwards;
|
||||
|
||||
@keyframes floatIn {
|
||||
to {
|
||||
width: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`
|
||||
interface TokenInfo {
|
||||
tokenName: string
|
||||
tokenSymbol: string
|
||||
}
|
||||
|
||||
export default function ShareButton(tokenInfo: TokenInfo) {
|
||||
const theme = useTheme()
|
||||
const node = useRef<HTMLDivElement | null>(null)
|
||||
const open = useModalIsOpen(ApplicationModal.SHARE)
|
||||
const toggleShare = useToggleModal(ApplicationModal.SHARE)
|
||||
useOnClickOutside(node, open ? toggleShare : undefined)
|
||||
const [showCopied, setShowCopied] = useState(false)
|
||||
const positionX = (window.screen.width - TWITTER_WIDTH) / 2
|
||||
const positionY = (window.screen.height - TWITTER_HEIGHT) / 2
|
||||
|
||||
const shareTweet = () => {
|
||||
toggleShare()
|
||||
window.open(
|
||||
`https://twitter.com/intent/tweet?text=Check%20out%20${tokenInfo.tokenName}%20(${tokenInfo.tokenSymbol})%20https://app.uniswap.org/%23/tokens/${tokenInfo.tokenSymbol}%20via%20@uniswap`,
|
||||
'newwindow',
|
||||
`left=${positionX}, top=${positionY}, width=${TWITTER_WIDTH}, height=${TWITTER_HEIGHT}`
|
||||
)
|
||||
}
|
||||
const copyLink = () => {
|
||||
navigator.clipboard.writeText(window.location.href).then(
|
||||
function handleClipboardWriteSuccess() {
|
||||
setShowCopied(true)
|
||||
toggleShare()
|
||||
setTimeout(() => setShowCopied(false), 3000)
|
||||
},
|
||||
function error() {
|
||||
console.error('Clipboard copy failed.')
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ShareButtonDisplay ref={node}>
|
||||
<Share size={18} onClick={toggleShare} aria-label={`ShareOptions`} />
|
||||
{open && (
|
||||
<ShareActions>
|
||||
<ShareAction onClick={copyLink}>
|
||||
<Link color={theme.textSecondary} size={18} />
|
||||
Copy link
|
||||
</ShareAction>
|
||||
|
||||
<ShareAction onClick={shareTweet}>
|
||||
<Twitter color={theme.textSecondary} size={18} />
|
||||
Share to Twitter
|
||||
</ShareAction>
|
||||
</ShareActions>
|
||||
)}
|
||||
</ShareButtonDisplay>
|
||||
<LinkCopied show={showCopied}>
|
||||
<Check color={theme.accentSuccess} />
|
||||
Link Copied
|
||||
</LinkCopied>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ParentSize } from '@visx/responsive'
|
||||
import PriceChart from 'components/Charts/PriceChart'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import { VerifiedIcon } from 'components/TokenSafety/TokenSafetyIcon'
|
||||
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { checkWarning } from 'constants/tokenSafety'
|
||||
import { useCurrency, useIsUserAddedToken, useToken } from 'hooks/Tokens'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { darken } from 'polished'
|
||||
import { useCallback } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { ArrowLeft, Copy, Heart } from 'react-feather'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { MOBILE_MEDIA_BREAKPOINT } from '../constants'
|
||||
import { favoritesAtom, useToggleFavorite } from '../state'
|
||||
import { ClickFavorited } from '../TokenTable/TokenRow'
|
||||
import Resource from './Resource'
|
||||
import ShareButton from './ShareButton'
|
||||
|
||||
export const AboutSection = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 24px 0px;
|
||||
`
|
||||
export const AboutHeader = styled.span`
|
||||
font-size: 28px;
|
||||
line-height: 36px;
|
||||
`
|
||||
export const BreadcrumbNavLink = styled(Link)`
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
text-decoration: none;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
}
|
||||
`
|
||||
export const ChartHeader = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
gap: 4px;
|
||||
margin-bottom: 24px;
|
||||
`
|
||||
const ContractAddress = styled.button`
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => darken(0.1, theme.textPrimary)};
|
||||
}
|
||||
`
|
||||
export const ContractAddressSection = styled.div`
|
||||
padding: 24px 0px;
|
||||
`
|
||||
const Contract = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 14px;
|
||||
gap: 4px;
|
||||
`
|
||||
export const ChartContainer = styled.div`
|
||||
display: flex;
|
||||
height: 436px;
|
||||
align-items: center;
|
||||
`
|
||||
export const Stat = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 14px;
|
||||
min-width: 168px;
|
||||
flex: 1;
|
||||
gap: 4px;
|
||||
padding: 24px 0px;
|
||||
`
|
||||
const StatPrice = styled.span`
|
||||
font-size: 28px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
export const StatsSection = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
export const StatPair = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
export const TokenNameCell = styled.div`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
align-items: center;
|
||||
`
|
||||
const TokenActions = styled.div`
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
export const TokenInfoContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`
|
||||
const TokenSymbol = styled.span`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
export const TopArea = styled.div`
|
||||
max-width: 832px;
|
||||
`
|
||||
export const ResourcesContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
`
|
||||
const FullAddress = styled.span`
|
||||
@media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
const TruncatedAddress = styled.span`
|
||||
display: none;
|
||||
@media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
|
||||
display: flex;
|
||||
}
|
||||
`
|
||||
const NetworkBadge = styled.div<{ networkColor?: string; backgroundColor?: string }>`
|
||||
border-radius: 5px;
|
||||
padding: 4px 8px;
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
color: ${({ theme, networkColor }) => networkColor ?? theme.textPrimary};
|
||||
background-color: ${({ theme, backgroundColor }) => backgroundColor ?? theme.backgroundSurface};
|
||||
`
|
||||
|
||||
export default function LoadedTokenDetail({ address }: { address: string }) {
|
||||
const theme = useTheme()
|
||||
const token = useToken(address)
|
||||
const currency = useCurrency(address)
|
||||
const favoriteTokens = useAtomValue<string[]>(favoritesAtom)
|
||||
const isFavorited = favoriteTokens.includes(address)
|
||||
const toggleFavorite = useToggleFavorite(address)
|
||||
const warning = checkWarning(address)
|
||||
const navigate = useNavigate()
|
||||
const isUserAddedToken = useIsUserAddedToken(token)
|
||||
const [warningModalOpen, setWarningModalOpen] = useState(!!warning && !isUserAddedToken)
|
||||
|
||||
const handleDismissWarning = useCallback(() => {
|
||||
setWarningModalOpen(false)
|
||||
}, [setWarningModalOpen])
|
||||
const chainInfo = getChainInfo(token?.chainId)
|
||||
const networkLabel = chainInfo?.label
|
||||
const networkBadgebackgroundColor = chainInfo?.backgroundColor
|
||||
|
||||
// catch token error and loading state
|
||||
if (!token || !token.name || !token.symbol) {
|
||||
return <div>No Token</div>
|
||||
}
|
||||
const tokenName = token.name
|
||||
const tokenSymbol = token.symbol
|
||||
|
||||
// TODO: format price, add sparkline
|
||||
const aboutToken =
|
||||
'Ethereum is a decentralized computing platform that uses ETH (Ether) to pay transaction fees (gas). Developers can use Ethereum to run decentralized applications (dApps) and issue new crypto assets, known as Ethereum tokens.'
|
||||
const tokenMarketCap = '23.02B'
|
||||
const tokenVolume = '1.6B'
|
||||
const truncatedTokenAddress = `${address.slice(0, 4)}...${address.slice(-3)}`
|
||||
|
||||
return (
|
||||
<TopArea>
|
||||
<BreadcrumbNavLink to="/explore">
|
||||
<ArrowLeft size={14} /> Explore
|
||||
</BreadcrumbNavLink>
|
||||
<ChartHeader>
|
||||
<TokenInfoContainer>
|
||||
<TokenNameCell>
|
||||
<CurrencyLogo currency={currency} size={'32px'} />
|
||||
{tokenName} <TokenSymbol>{tokenSymbol}</TokenSymbol>
|
||||
{!warning && <VerifiedIcon size="24px" />}
|
||||
{networkBadgebackgroundColor && (
|
||||
<NetworkBadge networkColor={chainInfo?.color} backgroundColor={networkBadgebackgroundColor}>
|
||||
{networkLabel}
|
||||
</NetworkBadge>
|
||||
)}
|
||||
</TokenNameCell>
|
||||
<TokenActions>
|
||||
<ShareButton tokenName={tokenName} tokenSymbol={tokenSymbol} />
|
||||
<ClickFavorited onClick={toggleFavorite}>
|
||||
<Heart
|
||||
size={15}
|
||||
color={isFavorited ? theme.accentAction : theme.textSecondary}
|
||||
fill={isFavorited ? theme.accentAction : theme.none}
|
||||
/>
|
||||
</ClickFavorited>
|
||||
</TokenActions>
|
||||
</TokenInfoContainer>
|
||||
<ChartContainer>
|
||||
<ParentSize>{({ width, height }) => <PriceChart width={width} height={height} />}</ParentSize>
|
||||
</ChartContainer>
|
||||
</ChartHeader>
|
||||
<AboutSection>
|
||||
<AboutHeader>
|
||||
<Trans>About</Trans>
|
||||
</AboutHeader>
|
||||
{aboutToken}
|
||||
<ResourcesContainer>
|
||||
<Resource name={'Etherscan'} link={'https://etherscan.io/'} />
|
||||
<Resource name={'Protocol Info'} link={`https://info.uniswap.org/#/tokens/${address}`} />
|
||||
</ResourcesContainer>
|
||||
</AboutSection>
|
||||
<StatsSection>
|
||||
<StatPair>
|
||||
<Stat>
|
||||
Market cap<StatPrice>${tokenMarketCap}</StatPrice>
|
||||
</Stat>
|
||||
<Stat>
|
||||
{/* TODO: connect to chart's selected time */}
|
||||
1h volume
|
||||
<StatPrice>${tokenVolume}</StatPrice>
|
||||
</Stat>
|
||||
</StatPair>
|
||||
<StatPair>
|
||||
<Stat>
|
||||
52W low
|
||||
<StatPrice>$1,790.01</StatPrice>
|
||||
</Stat>
|
||||
<Stat>
|
||||
52W high
|
||||
<StatPrice>$4,420.71</StatPrice>
|
||||
</Stat>
|
||||
</StatPair>
|
||||
</StatsSection>
|
||||
<ContractAddressSection>
|
||||
<Contract>
|
||||
Contract Address
|
||||
<ContractAddress onClick={() => navigator.clipboard.writeText(address)}>
|
||||
<FullAddress>{address}</FullAddress>
|
||||
<TruncatedAddress>{truncatedTokenAddress}</TruncatedAddress>
|
||||
<Copy size={13} color={theme.textSecondary} />
|
||||
</ContractAddress>
|
||||
</Contract>
|
||||
</ContractAddressSection>
|
||||
<TokenSafetyModal
|
||||
isOpen={warningModalOpen}
|
||||
tokenAddress={address}
|
||||
onCancel={() => navigate(-1)}
|
||||
onContinue={handleDismissWarning}
|
||||
/>
|
||||
</TopArea>
|
||||
)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { useAtom } from 'jotai'
|
||||
import { Heart } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { SMALL_MOBILE_MEDIA_BREAKPOINT } from '../constants'
|
||||
import { showFavoritesAtom } from '../state'
|
||||
|
||||
const FavoriteButtonContent = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
`
|
||||
const StyledFavoriteButton = styled.button<{ active: boolean }>`
|
||||
padding: 0px 16px;
|
||||
border-radius: 12px;
|
||||
background-color: ${({ theme, active }) => (active ? theme.accentAction : theme.none)};
|
||||
border: 1px solid ${({ theme, active }) => (active ? theme.accentActive : theme.backgroundOutline)};
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
|
||||
:hover {
|
||||
background-color: ${({ theme, active }) => !active && theme.backgroundContainer};
|
||||
}
|
||||
`
|
||||
const FavoriteText = styled.span`
|
||||
@media only screen and (max-width: ${SMALL_MOBILE_MEDIA_BREAKPOINT}) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
export default function FavoriteButton() {
|
||||
const theme = useTheme()
|
||||
const [showFavorites, setShowFavorites] = useAtom(showFavoritesAtom)
|
||||
return (
|
||||
<StyledFavoriteButton onClick={() => setShowFavorites(!showFavorites)} active={showFavorites}>
|
||||
<FavoriteButtonContent>
|
||||
<Heart size={17} color={theme.textPrimary} fill={theme.none} />
|
||||
<FavoriteText>Favorites</FavoriteText>
|
||||
</FavoriteButtonContent>
|
||||
</StyledFavoriteButton>
|
||||
)
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { useAtom } from 'jotai'
|
||||
import { useRef } from 'react'
|
||||
import { Check, ChevronDown, ChevronUp } from 'react-feather'
|
||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { MEDIUM_MEDIA_BREAKPOINT } from '../constants'
|
||||
import { filterNetworkAtom } from '../state'
|
||||
|
||||
const NETWORKS = [
|
||||
SupportedChainId.MAINNET,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.POLYGON,
|
||||
SupportedChainId.OPTIMISM,
|
||||
]
|
||||
|
||||
const InternalMenuItem = styled.div`
|
||||
flex: 1;
|
||||
padding: 12px 8px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
`
|
||||
|
||||
const InternalLinkMenuItem = styled(InternalMenuItem)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
border-radius: 12px;
|
||||
|
||||
:hover {
|
||||
background-color: ${({ theme }) => theme.hoverState};
|
||||
text-decoration: none;
|
||||
}
|
||||
`
|
||||
const MenuTimeFlyout = styled.span`
|
||||
min-width: 200px;
|
||||
max-height: 350px;
|
||||
overflow: auto;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
box-shadow: ${({ theme }) => theme.flyoutDropShadow};
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
border-radius: 12px;
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 16px;
|
||||
position: absolute;
|
||||
top: 48px;
|
||||
z-index: 100;
|
||||
left: 0px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
bottom: unset;
|
||||
right: 0;
|
||||
left: unset;
|
||||
`};
|
||||
`
|
||||
|
||||
const StyledMenuButton = styled.button<{ open: boolean }>`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: ${({ theme, open }) => (open ? theme.blue200 : theme.textPrimary)};
|
||||
background-color: transparent;
|
||||
background-color: ${({ theme, open }) => (open ? theme.accentActionSoft : theme.none)};
|
||||
border: 1px solid ${({ theme, open }) => (open ? theme.accentActiveSoft : theme.backgroundOutline)};
|
||||
margin: 0;
|
||||
padding: 6px 12px 6px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
|
||||
:hover,
|
||||
:focus {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
background-color: ${({ theme, open }) => !open && theme.backgroundContainer};
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-top: 2px;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledMenu = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border: none;
|
||||
text-align: left;
|
||||
width: 160px;
|
||||
|
||||
@media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) {
|
||||
flex: 1;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledMenuContent = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border: none;
|
||||
width: 100%;
|
||||
font-weight: 600;
|
||||
vertical-align: middle;
|
||||
`
|
||||
|
||||
const Chevron = styled.span<{ open: boolean }>`
|
||||
padding-top: 1px;
|
||||
color: ${({ open, theme }) => (open ? theme.blue200 : theme.textSecondary)};
|
||||
`
|
||||
const NetworkLabel = styled.div`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
`
|
||||
const Logo = styled.img`
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
`
|
||||
const CheckContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: flex-end;
|
||||
`
|
||||
|
||||
// TODO: change this to reflect data pipeline
|
||||
export default function NetworkFilter() {
|
||||
const theme = useTheme()
|
||||
const node = useRef<HTMLDivElement | null>(null)
|
||||
const open = useModalIsOpen(ApplicationModal.NETWORK_FILTER)
|
||||
const toggleMenu = useToggleModal(ApplicationModal.NETWORK_FILTER)
|
||||
useOnClickOutside(node, open ? toggleMenu : undefined)
|
||||
const [activeNetwork, setNetwork] = useAtom(filterNetworkAtom)
|
||||
const { label, logoUrl } = getChainInfo(activeNetwork)
|
||||
|
||||
return (
|
||||
<StyledMenu ref={node}>
|
||||
<StyledMenuButton onClick={toggleMenu} aria-label={`networkFilter`} open={open}>
|
||||
<StyledMenuContent>
|
||||
<NetworkLabel>
|
||||
<Logo src={logoUrl} /> {label}
|
||||
</NetworkLabel>
|
||||
<Chevron open={open}>
|
||||
{open ? <ChevronUp size={15} viewBox="0 0 24 20" /> : <ChevronDown size={15} viewBox="0 0 24 20" />}
|
||||
</Chevron>
|
||||
</StyledMenuContent>
|
||||
</StyledMenuButton>
|
||||
{open && (
|
||||
<MenuTimeFlyout>
|
||||
{NETWORKS.map((network) => (
|
||||
<InternalLinkMenuItem
|
||||
key={network}
|
||||
onClick={() => {
|
||||
setNetwork(network)
|
||||
toggleMenu()
|
||||
}}
|
||||
>
|
||||
<NetworkLabel>
|
||||
<Logo src={getChainInfo(network).logoUrl} /> {getChainInfo(network).label}
|
||||
</NetworkLabel>
|
||||
{network === activeNetwork && (
|
||||
<CheckContainer>
|
||||
<Check size={16} color={theme.accentAction} />
|
||||
</CheckContainer>
|
||||
)}
|
||||
</InternalLinkMenuItem>
|
||||
))}
|
||||
</MenuTimeFlyout>
|
||||
)}
|
||||
</StyledMenu>
|
||||
)
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import searchIcon from 'assets/svg/search.svg'
|
||||
import xIcon from 'assets/svg/x.svg'
|
||||
import { useAtom } from 'jotai'
|
||||
import { useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { MEDIUM_MEDIA_BREAKPOINT } from '../constants'
|
||||
import { filterStringAtom } from '../state'
|
||||
const ICON_SIZE = '20px'
|
||||
|
||||
const SearchBarContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
`
|
||||
const SearchInput = styled.input<{ expanded: boolean }>`
|
||||
background: no-repeat scroll 7px 7px;
|
||||
background-image: ${({ expanded }) => !expanded && `url(${searchIcon})`};
|
||||
background-size: 20px 20px;
|
||||
background-position: 11px center;
|
||||
background-color: ${({ theme }) => theme.none};
|
||||
border-radius: 12px;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
height: 100%;
|
||||
width: ${({ expanded }) => (expanded ? '100%' : '44px')};
|
||||
font-size: 16px;
|
||||
padding-left: 18px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
transition: width 0.75s cubic-bezier(0, 0.795, 0, 1);
|
||||
|
||||
:hover {
|
||||
cursor: ${({ expanded }) => !expanded && 'pointer'};
|
||||
background-color: ${({ theme }) => theme.backgroundContainer};
|
||||
}
|
||||
|
||||
:focus {
|
||||
outline: none;
|
||||
background-color: ${({ theme }) => theme.accentActionSoft};
|
||||
border: none;
|
||||
}
|
||||
::placeholder {
|
||||
color: ${({ expanded, theme }) => expanded && theme.textTertiary};
|
||||
}
|
||||
::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
height: ${ICON_SIZE};
|
||||
width: ${ICON_SIZE};
|
||||
background-image: url(${xIcon});
|
||||
margin-right: 10px;
|
||||
background-size: ${ICON_SIZE} ${ICON_SIZE};
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) {
|
||||
width: 100%;
|
||||
}
|
||||
`
|
||||
|
||||
export default function SearchBar() {
|
||||
const [filterString, setFilterString] = useAtom(filterStringAtom)
|
||||
const [isExpanded, setExpanded] = useState(false)
|
||||
return (
|
||||
<SearchBarContainer>
|
||||
<SearchInput
|
||||
expanded={isExpanded}
|
||||
type="search"
|
||||
placeholder="Search by name or token address"
|
||||
id="searchBar"
|
||||
onBlur={() => isExpanded && filterString.length === 0 && setExpanded(false)}
|
||||
onFocus={() => setExpanded(true)}
|
||||
autoComplete="off"
|
||||
value={filterString}
|
||||
onChange={({ target: { value } }) => setFilterString(value)}
|
||||
/>
|
||||
</SearchBarContainer>
|
||||
)
|
||||
}
|
||||
@@ -1,514 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent } from 'components/AmplitudeAnalytics'
|
||||
import { EventName } from 'components/AmplitudeAnalytics/constants'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import { useCurrency, useToken } from 'hooks/Tokens'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { TimePeriod, TokenData } from 'hooks/useTopTokens'
|
||||
import { useAtom } from 'jotai'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { ReactNode } from 'react'
|
||||
import { ArrowDown, ArrowDownRight, ArrowUp, ArrowUpRight, Heart } from 'react-feather'
|
||||
import { Link } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { formatAmount, formatDollarAmount } from 'utils/formatDollarAmt'
|
||||
|
||||
import {
|
||||
LARGE_MEDIA_BREAKPOINT,
|
||||
MAX_WIDTH_MEDIA_BREAKPOINT,
|
||||
MEDIUM_MEDIA_BREAKPOINT,
|
||||
MOBILE_MEDIA_BREAKPOINT,
|
||||
SMALL_MEDIA_BREAKPOINT,
|
||||
} from '../constants'
|
||||
import { LoadingBubble } from '../loading'
|
||||
import {
|
||||
favoritesAtom,
|
||||
filterNetworkAtom,
|
||||
filterStringAtom,
|
||||
filterTimeAtom,
|
||||
sortCategoryAtom,
|
||||
sortDirectionAtom,
|
||||
useSetSortCategory,
|
||||
useToggleFavorite,
|
||||
} from '../state'
|
||||
import { Category, SortDirection } from '../types'
|
||||
import { TIME_DISPLAYS } from './TimeSelector'
|
||||
|
||||
const ArrowCell = styled.div`
|
||||
padding-left: 2px;
|
||||
display: flex;
|
||||
`
|
||||
const Cell = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
const StyledTokenRow = styled.div`
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr 1fr 7fr 4fr 4fr 4fr 4fr 5fr;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
|
||||
max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT};
|
||||
min-width: 390px;
|
||||
padding: 0px 12px;
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.accentActionSoft};
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT}) {
|
||||
grid-template-columns: 1.7fr 1fr 6.5fr 4.5fr 4.5fr 4.5fr 4.5fr;
|
||||
width: fit-content;
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${LARGE_MEDIA_BREAKPOINT}) {
|
||||
grid-template-columns: 1.7fr 1fr 7.5fr 4.5fr 4.5fr 4.5fr;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) {
|
||||
grid-template-columns: 1.2fr 1fr 8fr 5fr 5fr;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
|
||||
grid-template-columns: 1fr 7fr 4fr 4fr 0.5px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
min-width: unset;
|
||||
border-bottom: 0.5px solid ${({ theme }) => theme.backgroundContainer};
|
||||
padding: 0px 12px;
|
||||
|
||||
:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
`
|
||||
export const ClickFavorited = styled.span`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
}
|
||||
`
|
||||
|
||||
const ClickableContent = styled.div`
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
`
|
||||
const ClickableName = styled(ClickableContent)`
|
||||
gap: 8px;
|
||||
`
|
||||
const FavoriteCell = styled(Cell)`
|
||||
min-width: 40px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
fill: none;
|
||||
|
||||
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
const StyledHeaderRow = styled(StyledTokenRow)`
|
||||
border-bottom: 1px solid;
|
||||
border-color: ${({ theme }) => theme.backgroundOutline};
|
||||
border-radius: 8px 8px 0px 0px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 12px;
|
||||
height: 48px;
|
||||
line-height: 16px;
|
||||
padding: 0px 12px;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT}) {
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
|
||||
justify-content: space-between;
|
||||
padding: 0px 12px;
|
||||
}
|
||||
`
|
||||
const ListNumberCell = styled(Cell)`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
min-width: 32px;
|
||||
|
||||
@media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
const DataCell = styled(Cell)<{ sortable: boolean }>`
|
||||
justify-content: flex-end;
|
||||
min-width: 80px;
|
||||
user-select: ${({ sortable }) => (sortable ? 'none' : 'unset')};
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme, sortable }) => sortable && theme.white};
|
||||
background-color: ${({ theme, sortable }) => sortable && theme.accentActionSoft};
|
||||
}
|
||||
`
|
||||
const MarketCapCell = styled(DataCell)`
|
||||
@media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
const NameCell = styled(Cell)`
|
||||
justify-content: flex-start;
|
||||
padding-left: 8px;
|
||||
min-width: 200px;
|
||||
gap: 8px;
|
||||
|
||||
@media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
|
||||
padding-right: 8px;
|
||||
}
|
||||
`
|
||||
const PriceCell = styled(DataCell)``
|
||||
const PercentChangeCell = styled(DataCell)`
|
||||
@media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
const PercentChangeInfoCell = styled(Cell)`
|
||||
display: none;
|
||||
|
||||
@media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
`
|
||||
const PriceInfoCell = styled(Cell)`
|
||||
justify-content: flex-end;
|
||||
flex: 1;
|
||||
|
||||
@media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
`
|
||||
const SortArrowCell = styled(Cell)`
|
||||
padding-right: 2px;
|
||||
`
|
||||
const HeaderCellWrapper = styled.span<{ onClick?: () => void }>`
|
||||
align-items: center;
|
||||
cursor: ${({ onClick }) => (onClick ? 'pointer' : 'unset')};
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: flex-end;
|
||||
padding-right: 8px;
|
||||
width: 100%;
|
||||
`
|
||||
const SparkLineCell = styled(Cell)`
|
||||
padding: 0px 24px;
|
||||
min-width: 120px;
|
||||
|
||||
@media only screen and (max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT}) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
const SparkLineImg = styled(Cell)<{ isPositive: boolean }>`
|
||||
max-width: 124px;
|
||||
max-height: 28px;
|
||||
flex-direction: column;
|
||||
transform: scale(1.2);
|
||||
|
||||
polyline {
|
||||
stroke: ${({ theme, isPositive }) => (isPositive ? theme.accentSuccess : theme.accentFailure)};
|
||||
}
|
||||
`
|
||||
const StyledLink = styled(Link)`
|
||||
text-decoration: none;
|
||||
`
|
||||
const TokenInfoCell = styled(Cell)`
|
||||
gap: 8px;
|
||||
line-height: 24px;
|
||||
font-size: 16px;
|
||||
|
||||
@media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
|
||||
justify-content: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 0px;
|
||||
width: max-content;
|
||||
font-weight: 500;
|
||||
}
|
||||
`
|
||||
const TokenName = styled.div`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 160px;
|
||||
white-space: nowrap;
|
||||
`
|
||||
const TokenSymbol = styled(Cell)`
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
|
||||
@media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
|
||||
font-size: 12px;
|
||||
height: 16px;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
`
|
||||
const VolumeCell = styled(DataCell)`
|
||||
@media only screen and (max-width: ${LARGE_MEDIA_BREAKPOINT}) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
const SmallLoadingBubble = styled(LoadingBubble)`
|
||||
width: 25%;
|
||||
`
|
||||
const MediumLoadingBubble = styled(LoadingBubble)`
|
||||
width: 65%;
|
||||
`
|
||||
const LongLoadingBubble = styled(LoadingBubble)`
|
||||
width: 90%;
|
||||
`
|
||||
const IconLoadingBubble = styled(LoadingBubble)`
|
||||
border-radius: 50%;
|
||||
width: 24px;
|
||||
`
|
||||
const SparkLineLoadingBubble = styled(LongLoadingBubble)`
|
||||
height: 4px;
|
||||
`
|
||||
|
||||
/* formatting for volume with timeframe header display */
|
||||
function getHeaderDisplay(category: string, timeframe: string): string {
|
||||
if (category === Category.volume) return `${TIME_DISPLAYS[timeframe]} ${category}`
|
||||
return category
|
||||
}
|
||||
|
||||
/* Get singular header cell for header row */
|
||||
function HeaderCell({
|
||||
category,
|
||||
sortable,
|
||||
}: {
|
||||
category: Category // TODO: change this to make it work for trans
|
||||
sortable: boolean
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
const sortDirection = useAtomValue<SortDirection>(sortDirectionAtom)
|
||||
const handleSortCategory = useSetSortCategory(category)
|
||||
const sortCategory = useAtomValue<Category>(sortCategoryAtom)
|
||||
const timeframe = useAtomValue<TimePeriod>(filterTimeAtom)
|
||||
|
||||
if (sortCategory === category) {
|
||||
return (
|
||||
<HeaderCellWrapper onClick={handleSortCategory}>
|
||||
<SortArrowCell>
|
||||
{sortDirection === SortDirection.increasing ? (
|
||||
<ArrowUp size={14} color={theme.accentActive} />
|
||||
) : (
|
||||
<ArrowDown size={14} color={theme.accentActive} />
|
||||
)}
|
||||
</SortArrowCell>
|
||||
{getHeaderDisplay(category, timeframe)}
|
||||
</HeaderCellWrapper>
|
||||
)
|
||||
}
|
||||
if (sortable) {
|
||||
return (
|
||||
<HeaderCellWrapper onClick={handleSortCategory}>
|
||||
<SortArrowCell>
|
||||
<ArrowUp size={14} visibility="hidden" />
|
||||
</SortArrowCell>
|
||||
{getHeaderDisplay(category, timeframe)}
|
||||
</HeaderCellWrapper>
|
||||
)
|
||||
}
|
||||
return <HeaderCellWrapper>{getHeaderDisplay(category, timeframe)}</HeaderCellWrapper>
|
||||
}
|
||||
|
||||
/* Token Row: skeleton row component */
|
||||
export function TokenRow({
|
||||
address,
|
||||
header,
|
||||
favorited,
|
||||
listNumber,
|
||||
tokenInfo,
|
||||
price,
|
||||
percentChange,
|
||||
marketCap,
|
||||
volume,
|
||||
sparkLine,
|
||||
}: {
|
||||
address: ReactNode
|
||||
header: boolean
|
||||
favorited: ReactNode
|
||||
listNumber: ReactNode
|
||||
tokenInfo: ReactNode
|
||||
price: ReactNode
|
||||
percentChange: ReactNode
|
||||
marketCap: ReactNode
|
||||
volume: ReactNode
|
||||
sparkLine: ReactNode
|
||||
}) {
|
||||
const rowCells = (
|
||||
<>
|
||||
<FavoriteCell>{favorited}</FavoriteCell>
|
||||
<ListNumberCell>{listNumber}</ListNumberCell>
|
||||
<NameCell>{tokenInfo}</NameCell>
|
||||
<PriceCell sortable={header}>{price}</PriceCell>
|
||||
<PercentChangeCell sortable={header}>{percentChange}</PercentChangeCell>
|
||||
<MarketCapCell sortable={header}>{marketCap}</MarketCapCell>
|
||||
<VolumeCell sortable={header}>{volume}</VolumeCell>
|
||||
<SparkLineCell>{sparkLine}</SparkLineCell>
|
||||
</>
|
||||
)
|
||||
if (header) return <StyledHeaderRow>{rowCells}</StyledHeaderRow>
|
||||
return <StyledTokenRow>{rowCells}</StyledTokenRow>
|
||||
}
|
||||
|
||||
/* Header Row: top header row component for table */
|
||||
export function HeaderRow() {
|
||||
return (
|
||||
<TokenRow
|
||||
address={null}
|
||||
header={true}
|
||||
favorited={null}
|
||||
listNumber={null}
|
||||
tokenInfo={<Trans>Token Name</Trans>}
|
||||
price={<HeaderCell category={Category.price} sortable />}
|
||||
percentChange={<HeaderCell category={Category.percentChange} sortable />}
|
||||
marketCap={<HeaderCell category={Category.marketCap} sortable />}
|
||||
volume={<HeaderCell category={Category.volume} sortable />}
|
||||
sparkLine={null}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/* Loading State: row component with loading bubbles */
|
||||
export function LoadingRow() {
|
||||
return (
|
||||
<TokenRow
|
||||
address={null}
|
||||
header={false}
|
||||
favorited={null}
|
||||
listNumber={<SmallLoadingBubble />}
|
||||
tokenInfo={
|
||||
<>
|
||||
<IconLoadingBubble />
|
||||
<MediumLoadingBubble />
|
||||
</>
|
||||
}
|
||||
price={<MediumLoadingBubble />}
|
||||
percentChange={<LoadingBubble />}
|
||||
marketCap={<LoadingBubble />}
|
||||
volume={<LoadingBubble />}
|
||||
sparkLine={<SparkLineLoadingBubble />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/* Loaded State: row component with token information */
|
||||
export default function LoadedRow({
|
||||
tokenAddress,
|
||||
tokenListIndex,
|
||||
tokenListLength,
|
||||
data,
|
||||
timePeriod,
|
||||
}: {
|
||||
tokenAddress: string
|
||||
tokenListIndex: number
|
||||
tokenListLength: number
|
||||
data: TokenData
|
||||
timePeriod: TimePeriod
|
||||
}) {
|
||||
const token = useToken(tokenAddress)
|
||||
const currency = useCurrency(tokenAddress)
|
||||
const tokenName = token?.name ?? ''
|
||||
const tokenSymbol = token?.symbol ?? ''
|
||||
const tokenData = data[tokenAddress]
|
||||
const theme = useTheme()
|
||||
const [favoriteTokens] = useAtom(favoritesAtom)
|
||||
const isFavorited = favoriteTokens.includes(tokenAddress)
|
||||
const toggleFavorite = useToggleFavorite(tokenAddress)
|
||||
const isPositive = Math.sign(tokenData.delta) > 0
|
||||
const filterString = useAtomValue(filterStringAtom)
|
||||
const filterNetwork = useAtomValue(filterNetworkAtom)
|
||||
const filterTime = useAtomValue(filterTimeAtom) // filter time period for top tokens table
|
||||
|
||||
const tokenPercentChangeInfo = (
|
||||
<>
|
||||
{tokenData.delta}%
|
||||
<ArrowCell>
|
||||
{isPositive ? (
|
||||
<ArrowUpRight size={16} color={theme.accentSuccess} />
|
||||
) : (
|
||||
<ArrowDownRight size={16} color={theme.accentFailure} />
|
||||
)}
|
||||
</ArrowCell>
|
||||
</>
|
||||
)
|
||||
|
||||
const exploreTokenSelectedEventProperties = {
|
||||
chain_id: filterNetwork,
|
||||
token_address: tokenAddress,
|
||||
token_symbol: token?.symbol,
|
||||
token_list_index: tokenListIndex,
|
||||
token_list_length: tokenListLength,
|
||||
time_frame: filterTime,
|
||||
search_token_address_input: filterString,
|
||||
}
|
||||
|
||||
const heartColor = isFavorited ? theme.accentActive : undefined
|
||||
// TODO: currency logo sizing mobile (32px) vs. desktop (24px)
|
||||
return (
|
||||
<StyledLink
|
||||
to={`/tokens/${tokenAddress}`}
|
||||
onClick={() => sendAnalyticsEvent(EventName.EXPLORE_TOKEN_ROW_CLICKED, exploreTokenSelectedEventProperties)}
|
||||
>
|
||||
<TokenRow
|
||||
address={tokenAddress}
|
||||
header={false}
|
||||
favorited={
|
||||
<ClickFavorited
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
toggleFavorite()
|
||||
}}
|
||||
>
|
||||
<Heart size={15} color={heartColor} fill={heartColor} />
|
||||
</ClickFavorited>
|
||||
}
|
||||
listNumber={tokenListIndex + 1}
|
||||
tokenInfo={
|
||||
<ClickableName>
|
||||
<CurrencyLogo currency={currency} />
|
||||
<TokenInfoCell>
|
||||
<TokenName>{tokenName}</TokenName>
|
||||
<TokenSymbol>{tokenSymbol}</TokenSymbol>
|
||||
</TokenInfoCell>
|
||||
</ClickableName>
|
||||
}
|
||||
price={
|
||||
<ClickableContent>
|
||||
<PriceInfoCell>
|
||||
{formatDollarAmount(tokenData.price)}
|
||||
<PercentChangeInfoCell>{tokenPercentChangeInfo}</PercentChangeInfoCell>
|
||||
</PriceInfoCell>
|
||||
</ClickableContent>
|
||||
}
|
||||
percentChange={<ClickableContent>{tokenPercentChangeInfo}</ClickableContent>}
|
||||
marketCap={<ClickableContent>{formatAmount(tokenData.marketCap).toUpperCase()}</ClickableContent>}
|
||||
volume={<ClickableContent>{formatAmount(tokenData.volume[timePeriod]).toUpperCase()}</ClickableContent>}
|
||||
sparkLine={<SparkLineImg dangerouslySetInnerHTML={{ __html: tokenData.sparkline }} isPositive={isPositive} />}
|
||||
/>
|
||||
</StyledLink>
|
||||
)
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
import {
|
||||
favoritesAtom,
|
||||
filterStringAtom,
|
||||
filterTimeAtom,
|
||||
showFavoritesAtom,
|
||||
sortCategoryAtom,
|
||||
sortDirectionAtom,
|
||||
} from 'components/Explore/state'
|
||||
import { useAllTokens } from 'hooks/Tokens'
|
||||
import useTopTokens, { TimePeriod, TokenData } from 'hooks/useTopTokens'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { ReactNode, useCallback, useMemo } from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { MAX_WIDTH_MEDIA_BREAKPOINT } from '../constants'
|
||||
import { Category, SortDirection } from '../types'
|
||||
import LoadedRow, { HeaderRow, LoadingRow } from './TokenRow'
|
||||
|
||||
const GridContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
|
||||
0px 24px 32px rgba(0, 0, 0, 0.01);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-radius: 8px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
`
|
||||
const NoTokenDisplay = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
align-items: center;
|
||||
padding: 0px 28px;
|
||||
gap: 8px;
|
||||
`
|
||||
const TokenRowsContainer = styled.div`
|
||||
padding: 4px 0px;
|
||||
`
|
||||
|
||||
function useFilteredTokens(addresses: string[]) {
|
||||
const filterString = useAtomValue(filterStringAtom)
|
||||
const favoriteTokens = useAtomValue(favoritesAtom)
|
||||
const showFavorites = useAtomValue(showFavoritesAtom)
|
||||
const shownTokens = showFavorites ? favoriteTokens : addresses
|
||||
const allTokens = useAllTokens()
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
shownTokens.filter((tokenAddress) => {
|
||||
const token = allTokens[tokenAddress]
|
||||
const tokenName = token?.name ?? ''
|
||||
const tokenSymbol = token?.symbol ?? ''
|
||||
|
||||
if (!filterString) {
|
||||
return true
|
||||
}
|
||||
const lowercaseFilterString = filterString.toLowerCase()
|
||||
const addressIncludesFilterString = tokenAddress.toLowerCase().includes(lowercaseFilterString)
|
||||
const nameIncludesFilterString = tokenName.toLowerCase().includes(lowercaseFilterString)
|
||||
const symbolIncludesFilterString = tokenSymbol.toLowerCase().includes(lowercaseFilterString)
|
||||
return nameIncludesFilterString || symbolIncludesFilterString || addressIncludesFilterString
|
||||
}),
|
||||
[allTokens, shownTokens, filterString]
|
||||
)
|
||||
}
|
||||
|
||||
function useSortedTokens(addresses: string[], tokenData: TokenData | null) {
|
||||
const sortCategory = useAtomValue(sortCategoryAtom)
|
||||
const sortDirection = useAtomValue(sortDirectionAtom)
|
||||
const timePeriod = useAtomValue<TimePeriod>(filterTimeAtom)
|
||||
|
||||
const sortFn = useCallback(
|
||||
(a: any, b: any) => {
|
||||
if (a > b) {
|
||||
return sortDirection === SortDirection.decreasing ? -1 : 1
|
||||
} else if (a < b) {
|
||||
return sortDirection === SortDirection.decreasing ? 1 : -1
|
||||
}
|
||||
return 0
|
||||
},
|
||||
[sortDirection]
|
||||
)
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
addresses.sort((token1Address, token2Address) => {
|
||||
if (!tokenData) {
|
||||
return 0
|
||||
}
|
||||
const token1 = tokenData[token1Address] as any
|
||||
const token2 = tokenData[token2Address] as any
|
||||
|
||||
if (!token1 || !token2 || !sortDirection || !sortCategory) {
|
||||
return 0
|
||||
}
|
||||
let a: number
|
||||
let b: number
|
||||
switch (sortCategory) {
|
||||
case Category.marketCap:
|
||||
a = token1.marketCap
|
||||
b = token2.marketCap
|
||||
break
|
||||
case Category.percentChange:
|
||||
a = token1.delta
|
||||
b = token2.delta
|
||||
break
|
||||
case Category.price:
|
||||
a = token1.price
|
||||
b = token2.price
|
||||
break
|
||||
case Category.volume:
|
||||
a = token1.volume[timePeriod]
|
||||
b = token2.volume[timePeriod]
|
||||
break
|
||||
}
|
||||
return sortFn(a, b)
|
||||
}),
|
||||
[addresses, tokenData, sortDirection, sortCategory, sortFn, timePeriod]
|
||||
)
|
||||
}
|
||||
|
||||
function NoTokensState({ message }: { message: ReactNode }) {
|
||||
return (
|
||||
<GridContainer>
|
||||
<HeaderRow />
|
||||
<NoTokenDisplay>{message}</NoTokenDisplay>
|
||||
</GridContainer>
|
||||
)
|
||||
}
|
||||
|
||||
function LoadingTokenTable() {
|
||||
return (
|
||||
<GridContainer>
|
||||
<HeaderRow />
|
||||
<TokenRowsContainer>
|
||||
{Array(10)
|
||||
.fill(0)
|
||||
.map((_item, index) => (
|
||||
<LoadingRow key={index} />
|
||||
))}
|
||||
</TokenRowsContainer>
|
||||
</GridContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default function TokenTable() {
|
||||
const { data, error, loading } = useTopTokens()
|
||||
const showFavorites = useAtomValue<boolean>(showFavoritesAtom)
|
||||
const timePeriod = useAtomValue<TimePeriod>(filterTimeAtom)
|
||||
const topTokenAddresses = data ? Object.keys(data) : []
|
||||
const filteredTokens = useFilteredTokens(topTokenAddresses)
|
||||
const filteredAndSortedTokens = useSortedTokens(filteredTokens, data)
|
||||
|
||||
/* loading and error state */
|
||||
if (loading) {
|
||||
return <LoadingTokenTable />
|
||||
} else if (error || data === null) {
|
||||
return (
|
||||
<NoTokensState
|
||||
message={
|
||||
<>
|
||||
<AlertTriangle size={16} />
|
||||
An error occured loading tokens. Please try again.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (showFavorites && filteredAndSortedTokens.length === 0) {
|
||||
return <NoTokensState message="You have no favorited tokens" />
|
||||
}
|
||||
|
||||
if (!showFavorites && filteredAndSortedTokens.length === 0) {
|
||||
return <NoTokensState message="No tokens found" />
|
||||
}
|
||||
|
||||
return (
|
||||
<GridContainer>
|
||||
<HeaderRow />
|
||||
<TokenRowsContainer>
|
||||
{filteredAndSortedTokens.map((tokenAddress, index) => (
|
||||
<LoadedRow
|
||||
key={tokenAddress}
|
||||
tokenAddress={tokenAddress}
|
||||
tokenListIndex={index}
|
||||
tokenListLength={filteredAndSortedTokens.length}
|
||||
data={data}
|
||||
timePeriod={timePeriod}
|
||||
/>
|
||||
))}
|
||||
</TokenRowsContainer>
|
||||
</GridContainer>
|
||||
)
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { TimePeriod } from 'hooks/useTopTokens'
|
||||
import { atom, useAtom } from 'jotai'
|
||||
import { atomWithReset, atomWithStorage } from 'jotai/utils'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import { Category, SortDirection } from './types'
|
||||
|
||||
export const favoritesAtom = atomWithStorage<string[]>('favorites', [])
|
||||
export const showFavoritesAtom = atomWithStorage<boolean>('showFavorites', false)
|
||||
export const filterStringAtom = atomWithReset<string>('')
|
||||
export const filterNetworkAtom = atom<SupportedChainId>(SupportedChainId.MAINNET)
|
||||
export const filterTimeAtom = atom<TimePeriod>(TimePeriod.day)
|
||||
export const sortCategoryAtom = atom<Category>(Category.marketCap)
|
||||
export const sortDirectionAtom = atom<SortDirection>(SortDirection.decreasing)
|
||||
|
||||
/* for favoriting tokens */
|
||||
export function useToggleFavorite(tokenAddress: string) {
|
||||
const [favoriteTokens, updateFavoriteTokens] = useAtom(favoritesAtom)
|
||||
|
||||
return useCallback(() => {
|
||||
let updatedFavoriteTokens
|
||||
if (favoriteTokens.includes(tokenAddress)) {
|
||||
updatedFavoriteTokens = favoriteTokens.filter((address: string) => {
|
||||
return address !== tokenAddress
|
||||
})
|
||||
} else {
|
||||
updatedFavoriteTokens = [...favoriteTokens, tokenAddress]
|
||||
}
|
||||
updateFavoriteTokens(updatedFavoriteTokens)
|
||||
}, [favoriteTokens, tokenAddress, updateFavoriteTokens])
|
||||
}
|
||||
|
||||
/* keep track of sort category for token table */
|
||||
export function useSetSortCategory(category: Category) {
|
||||
const [sortCategory, setSortCategory] = useAtom(sortCategoryAtom)
|
||||
const [sortDirection, setDirectionCategory] = useAtom(sortDirectionAtom)
|
||||
|
||||
return useCallback(() => {
|
||||
if (category === sortCategory) {
|
||||
const oppositeDirection =
|
||||
sortDirection === SortDirection.increasing ? SortDirection.decreasing : SortDirection.increasing
|
||||
setDirectionCategory(oppositeDirection)
|
||||
} else {
|
||||
setSortCategory(category)
|
||||
setDirectionCategory(SortDirection.decreasing)
|
||||
}
|
||||
}, [category, sortCategory, setSortCategory, sortDirection, setDirectionCategory])
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export enum Category {
|
||||
percentChange = '% Change',
|
||||
marketCap = 'Market Cap',
|
||||
price = 'Price',
|
||||
volume = 'Volume',
|
||||
}
|
||||
export enum SortDirection {
|
||||
increasing = 'Increasing',
|
||||
decreasing = 'Decreasing',
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { FeatureFlag, useUpdateFlag } from 'featureFlags'
|
||||
import { Phase0Variant, usePhase0Flag } from 'featureFlags/flags/phase0'
|
||||
import { ReactNode } from 'react'
|
||||
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { useModalIsOpen, useToggleFeatureFlags } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
@@ -10,7 +12,7 @@ const StyledModal = styled.div`
|
||||
position: fixed;
|
||||
display: flex;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
top: 50vh;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 400px;
|
||||
height: fit-content;
|
||||
@@ -39,49 +41,151 @@ const Row = styled.div`
|
||||
|
||||
const CloseButton = styled.button`
|
||||
cursor: pointer;
|
||||
background: ${({ theme }) => theme.none};
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
|
||||
const ToggleButton = styled.button`
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
|
||||
const Header = styled(Row)`
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
font-size: 16px;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
const FlagName = styled.span`
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
const FlagGroupName = styled.span`
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-weight: 600;
|
||||
`
|
||||
const FlagDescription = styled.span`
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
const FlagVariantSelection = styled.select`
|
||||
border-radius: 12px;
|
||||
padding: 8px;
|
||||
background: ${({ theme }) => theme.backgroundInteractive};
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
cursor: pointer;
|
||||
|
||||
:hover {
|
||||
background: ${({ theme }) => theme.backgroundOutline};
|
||||
}
|
||||
`
|
||||
|
||||
const FlagInfo = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-left: 8px;
|
||||
`
|
||||
|
||||
const SaveButton = styled.button`
|
||||
border-radius: 12px;
|
||||
padding: 8px;
|
||||
background: ${({ theme }) => theme.backgroundInteractive};
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
cursor: pointer;
|
||||
|
||||
:hover {
|
||||
background: ${({ theme }) => theme.backgroundOutline};
|
||||
}
|
||||
`
|
||||
|
||||
function Variant({ option }: { option: string }) {
|
||||
return <option value={option}>{option}</option>
|
||||
}
|
||||
|
||||
function FeatureFlagOption({
|
||||
variants,
|
||||
featureFlag,
|
||||
value,
|
||||
label,
|
||||
}: {
|
||||
variants: string[]
|
||||
interface FeatureFlagProps {
|
||||
variant: Record<string, string>
|
||||
featureFlag: FeatureFlag
|
||||
value: string
|
||||
label: string
|
||||
}) {
|
||||
}
|
||||
|
||||
function FeatureFlagGroup({ name, children }: PropsWithChildren<{ name: string }>) {
|
||||
// type FeatureFlagOption = { props: FeatureFlagProps }
|
||||
const togglableOptions = Children.toArray(children)
|
||||
.filter<ReactElement<FeatureFlagProps>>(
|
||||
(child): child is ReactElement<FeatureFlagProps> =>
|
||||
child instanceof Object && 'type' in child && child.type === FeatureFlagOption
|
||||
)
|
||||
.map(({ props }) => props)
|
||||
.filter(({ variant }) => {
|
||||
const values = Object.values(variant)
|
||||
return values.includes(BaseVariant.Control) && values.includes(BaseVariant.Enabled)
|
||||
})
|
||||
|
||||
const setFeatureFlags = useUpdateAtom(featureFlagSettings)
|
||||
const allEnabled = togglableOptions.every(({ value }) => value === BaseVariant.Enabled)
|
||||
const onToggle = useCallback(() => {
|
||||
setFeatureFlags((flags) => ({
|
||||
...flags,
|
||||
...togglableOptions.reduce(
|
||||
(flags, { featureFlag }) => ({
|
||||
...flags,
|
||||
[featureFlag]: allEnabled ? BaseVariant.Control : BaseVariant.Enabled,
|
||||
}),
|
||||
{}
|
||||
),
|
||||
}))
|
||||
}, [allEnabled, setFeatureFlags, togglableOptions])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row key={name}>
|
||||
<FlagGroupName>{name}</FlagGroupName>
|
||||
<ToggleButton onClick={onToggle}>{allEnabled ? 'Disable' : 'Enable'} group</ToggleButton>
|
||||
</Row>
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function FeatureFlagOption({ variant, featureFlag, value, label }: FeatureFlagProps) {
|
||||
const updateFlag = useUpdateFlag()
|
||||
const [count, setCount] = useState(0)
|
||||
const featureFlags = useAtomValue(featureFlagSettings)
|
||||
|
||||
return (
|
||||
<Row key={featureFlag}>
|
||||
{featureFlag}: {label}
|
||||
<select
|
||||
<FlagInfo>
|
||||
<FlagName>{featureFlag}</FlagName>
|
||||
<FlagDescription>{label}</FlagDescription>
|
||||
</FlagInfo>
|
||||
<FlagVariantSelection
|
||||
id={featureFlag}
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
updateFlag(featureFlag, e.target.value)
|
||||
window.location.reload()
|
||||
setCount(count + 1)
|
||||
}}
|
||||
value={featureFlags[featureFlag]}
|
||||
>
|
||||
{variants.map((variant) => (
|
||||
{Object.values(variant).map((variant) => (
|
||||
<Variant key={variant} option={variant} />
|
||||
))}
|
||||
</select>
|
||||
</FlagVariantSelection>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
@@ -98,13 +202,18 @@ export default function FeatureFlagModal() {
|
||||
<X size={24} />
|
||||
</CloseButton>
|
||||
</Header>
|
||||
|
||||
<FeatureFlagOption
|
||||
variants={Object.values(Phase0Variant)}
|
||||
value={usePhase0Flag()}
|
||||
featureFlag={FeatureFlag.phase0}
|
||||
label="All Phase 0 changes (redesign, explore, header)."
|
||||
/>
|
||||
<FeatureFlagGroup name="Phase 1">
|
||||
<FeatureFlagOption variant={NftVariant} value={useNftFlag()} featureFlag={FeatureFlag.nft} label="NFTs" />
|
||||
</FeatureFlagGroup>
|
||||
<FeatureFlagGroup name="Debug">
|
||||
<FeatureFlagOption
|
||||
variant={TraceJsonRpcVariant}
|
||||
value={useTraceJsonRpcFlag()}
|
||||
featureFlag={FeatureFlag.traceJsonRpc}
|
||||
label="Enables JSON-RPC tracing"
|
||||
/>
|
||||
</FeatureFlagGroup>
|
||||
<SaveButton onClick={() => window.location.reload()}>Reload</SaveButton>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||