kopia lustrzana https://github.com/bugout-dev/moonstream
layouts
rodzic
6e9d5ef6ec
commit
9e9d787d9b
|
@ -1,10 +1,17 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { getLayout } from "../src/layouts/AppLayout";
|
||||
import { Spinner, Flex, Heading, Stack } from "@chakra-ui/react";
|
||||
import Scrollable from "../src/components/Scrollable";
|
||||
import useNFTs from "../src/core/hooks/useNFTs";
|
||||
import RangeSelector from "../src/components/RangeSelector";
|
||||
import StatsCard from "../src/components/StatsCard";
|
||||
import useWindowSize from "../src/core/hooks/useWindowSize";
|
||||
|
||||
const HOUR_KEY = "Hourly";
|
||||
const DAY_KEY = "Daily";
|
||||
|
@ -15,17 +22,84 @@ timeMap[DAY_KEY] = "day";
|
|||
timeMap[WEEK_KEY] = "week";
|
||||
|
||||
const Analytics = () => {
|
||||
const windowSize = useWindowSize();
|
||||
useEffect(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
document.title = `Analytics: Page under construction`;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const [nodesReady, setNodeReady] = useState({
|
||||
ntx: false,
|
||||
values: false,
|
||||
mints: false,
|
||||
});
|
||||
|
||||
const nTxRef_ = useRef();
|
||||
const valueRef_ = useRef();
|
||||
const mintsRef_ = useRef();
|
||||
|
||||
const nTxRef = useCallback(
|
||||
(node) => {
|
||||
if (node !== null && !nodesReady.ntx) {
|
||||
setNodeReady({ ...nodesReady, ntx: true });
|
||||
nTxRef_.current = node;
|
||||
}
|
||||
},
|
||||
[nodesReady]
|
||||
);
|
||||
const valueRef = useCallback(
|
||||
(node) => {
|
||||
if (node !== null && !nodesReady.values) {
|
||||
setNodeReady({ ...nodesReady, values: true });
|
||||
valueRef_.current = node;
|
||||
}
|
||||
},
|
||||
[nodesReady]
|
||||
);
|
||||
const mintsRef = useCallback(
|
||||
(node) => {
|
||||
if (node !== null && !nodesReady.mints) {
|
||||
setNodeReady({ ...nodesReady, mints: true });
|
||||
mintsRef_.current = node;
|
||||
}
|
||||
},
|
||||
[nodesReady]
|
||||
);
|
||||
|
||||
const [timeRange, setTimeRange] = useState(HOUR_KEY);
|
||||
const { nftCache } = useNFTs();
|
||||
|
||||
if (nftCache.isLoading) return <Spinner />;
|
||||
useLayoutEffect(() => {
|
||||
const items = [nTxRef_, valueRef_, mintsRef_];
|
||||
console.log("useeffect fired");
|
||||
if (nTxRef_.current) {
|
||||
var firstItemInCurrentRow = items[0];
|
||||
items.forEach((item) => {
|
||||
if (item.current) {
|
||||
if (item !== firstItemInCurrentRow) {
|
||||
// Check if the current item is at the same
|
||||
// height as the first item in the current row.
|
||||
if (
|
||||
item.current.offsetTop === firstItemInCurrentRow.current.offsetTop
|
||||
) {
|
||||
item.current.style.borderLeft =
|
||||
"3px dashed var(--chakra-colors-gray-600)";
|
||||
} else {
|
||||
// This item was lower, it must be
|
||||
// the first in a new row.
|
||||
firstItemInCurrentRow = item;
|
||||
item.current.style.borderLeft = "0px dashed black";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
firstItemInCurrentRow = item;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [nodesReady, windowSize]);
|
||||
|
||||
if (nftCache.isLoading) return <Spinner />;
|
||||
return (
|
||||
<Scrollable>
|
||||
<Flex
|
||||
|
@ -37,13 +111,14 @@ const Analytics = () => {
|
|||
alignItems="center"
|
||||
minH="100vh"
|
||||
>
|
||||
<Heading as="h1" py={4}>
|
||||
<Heading as="h1" py={4} fontSize={["md", "xl"]}>
|
||||
NFT market analysis
|
||||
</Heading>
|
||||
<RangeSelector
|
||||
placeSelf="flex-start"
|
||||
initialRange={timeRange}
|
||||
ranges={Object.keys(timeMap)}
|
||||
size={["sm", "md", null]}
|
||||
onChange={(e) => setTimeRange(e)}
|
||||
/>
|
||||
<Stack
|
||||
|
@ -53,24 +128,31 @@ const Analytics = () => {
|
|||
h="auto"
|
||||
direction="row"
|
||||
minW="240px"
|
||||
spacing={[2, 0, null]}
|
||||
spacing={[0, 0, null]}
|
||||
boxShadow="md"
|
||||
borderRadius="lg"
|
||||
bgColor="gray.100"
|
||||
// divider={<StackDivider borderColor="gray.800" />}
|
||||
>
|
||||
<StatsCard
|
||||
ref={(node) => nTxRef(node)}
|
||||
// borderTopLeftRadius="inherit"
|
||||
labelKey="transactions"
|
||||
timeRange={timeMap[timeRange]}
|
||||
netLabel="Ethereum mainnet"
|
||||
label="Number of transactions"
|
||||
/>
|
||||
<StatsCard
|
||||
ref={(node) => valueRef(node)}
|
||||
labelKey="values"
|
||||
timeRange={timeMap[timeRange]}
|
||||
netLabel="Ethereum mainnet"
|
||||
label="Value of transactions"
|
||||
/>
|
||||
<StatsCard
|
||||
ref={(node) => mintsRef(node)}
|
||||
// borderTopRightRadius="inherit"
|
||||
// borderRightWidth="0"
|
||||
labelKey="mints"
|
||||
timeRange={timeMap[timeRange]}
|
||||
netLabel="Ethereum mainnet"
|
||||
|
|
|
@ -17,13 +17,13 @@ const AccountIconButton = (props) => {
|
|||
const { logout } = useLogout();
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
<Menu {...props}>
|
||||
<MenuButton
|
||||
{...props}
|
||||
variant="inherit"
|
||||
colorScheme="inherit"
|
||||
as={IconButton}
|
||||
aria-label="Account menu"
|
||||
icon={<RiAccountCircleLine size="26px" />}
|
||||
// variant="outline"
|
||||
icon={<RiAccountCircleLine m={0} size="26px" />}
|
||||
color="gray.100"
|
||||
/>
|
||||
<MenuList
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import React, { useEffect, useState, useRef } from "react";
|
||||
import { Stack, Container, chakra } from "@chakra-ui/react";
|
||||
|
||||
const RangeSelector_ = ({ className, ranges, onChange, initialRange }) => {
|
||||
const RangeSelector_ = ({
|
||||
className,
|
||||
ranges,
|
||||
onChange,
|
||||
initialRange,
|
||||
size,
|
||||
}) => {
|
||||
const [range, setRange] = useState(initialRange ?? ranges[0]);
|
||||
const isFirstRun = useRef(true);
|
||||
|
||||
|
@ -20,11 +26,11 @@ const RangeSelector_ = ({ className, ranges, onChange, initialRange }) => {
|
|||
return (
|
||||
<Container
|
||||
key={`date-range-${className}-${idx}`}
|
||||
size="xs"
|
||||
bgColor={isActive ? "secondary.900" : "primary.50"}
|
||||
color={!isActive ? "primary.900" : "primary.50"}
|
||||
boxShadow="sm"
|
||||
borderRadius="md"
|
||||
fontSize={size}
|
||||
fontWeight="600"
|
||||
onClick={() => setRange(item)}
|
||||
_hover={{
|
||||
|
|
|
@ -20,7 +20,7 @@ const Scrollable = (props) => {
|
|||
const currentScroll = Math.ceil(getScrollPrecent(e) / 10);
|
||||
if (currentScroll > scrollDepth) {
|
||||
setScrollDepth(currentScroll);
|
||||
mixpanel.get_distinct_id() &&
|
||||
mixpanel?.get_distinct_id() &&
|
||||
mixpanel.people.increment({
|
||||
[`Scroll depth at: ${router.nextRouter.pathname}`]: currentScroll,
|
||||
});
|
||||
|
|
|
@ -9,17 +9,13 @@ const TIME_PERIOD = {
|
|||
previous: 1,
|
||||
};
|
||||
|
||||
const isNumberNotZero = (str) => {
|
||||
if (isNaN(Number(str) || Number(str) == 0)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
const isNumberNonzeroAndFinite = (str) => {
|
||||
return !(isNaN(Number(str)) || Number(str) === 0);
|
||||
};
|
||||
|
||||
const getEthValue = (string) => {
|
||||
const ether = web3.utils.fromWei(string, "ether");
|
||||
return nFormatter(ether, 3);
|
||||
return nFormatter(ether, 2);
|
||||
};
|
||||
|
||||
const nFormatter = (num, digits) => {
|
||||
|
@ -32,23 +28,22 @@ const nFormatter = (num, digits) => {
|
|||
{ value: 1e15, symbol: "P" },
|
||||
{ value: 1e18, symbol: "E" },
|
||||
];
|
||||
const rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
|
||||
var item = lookup
|
||||
|
||||
let item = lookup
|
||||
.slice()
|
||||
.reverse()
|
||||
.find(function (item) {
|
||||
return num >= item.value;
|
||||
.find(function (element) {
|
||||
return num >= element.value;
|
||||
});
|
||||
return item
|
||||
? (num / item.value).toFixed(digits).replace(rx, "$1") + item.symbol
|
||||
: "0";
|
||||
return item ? (num / item.value).toFixed(digits) + item.symbol : "0";
|
||||
};
|
||||
|
||||
const getChange = (a, b) => {
|
||||
if (isNumberNotZero(a) && isNumberNotZero(b)) {
|
||||
if (isNumberNonzeroAndFinite(a) && isNumberNonzeroAndFinite(b)) {
|
||||
let retval = (Math.abs(Number(a) - Number(b)) * 100) / Number(b);
|
||||
retval = retval > 9999 ? nFormatter(retval, 3) : retval;
|
||||
return retval.toFixed(2);
|
||||
retval =
|
||||
Math.abs(retval) > 9999 ? nFormatter(retval, 2) : retval.toFixed(2);
|
||||
return retval;
|
||||
} else {
|
||||
return "-";
|
||||
}
|
||||
|
@ -62,11 +57,19 @@ const getDiff = (a, b) => {
|
|||
}
|
||||
};
|
||||
|
||||
const getSign = (a) => {
|
||||
const isZeroOrPositive = (a) => {
|
||||
if (isNaN(a)) return "-";
|
||||
return Number(a) >= 0 ? true : false;
|
||||
};
|
||||
const StatsCard_ = ({ className, label, netLabel, labelKey, timeRange }) => {
|
||||
|
||||
const StatsCard_ = ({
|
||||
className,
|
||||
label,
|
||||
netLabel,
|
||||
labelKey,
|
||||
timeRange,
|
||||
innerRef,
|
||||
}) => {
|
||||
const { nftCache } = useNFTs();
|
||||
|
||||
const [nftData, setData] = useState();
|
||||
|
@ -87,29 +90,30 @@ const StatsCard_ = ({ className, label, netLabel, labelKey, timeRange }) => {
|
|||
|
||||
setData({
|
||||
dimension: labelKey === "values" ? "Eth" : "#",
|
||||
isValueIncrease: getSign(valueChange),
|
||||
isShareIncrease: getSign(shareChange),
|
||||
isValueIncrease: isZeroOrPositive(valueChange),
|
||||
isShareIncrease: isZeroOrPositive(shareChange),
|
||||
valueChange,
|
||||
shareChange,
|
||||
share,
|
||||
value:
|
||||
labelKey === "values"
|
||||
? getEthValue(cacheData.amount)
|
||||
: nFormatter(cacheData.amount, 3),
|
||||
: nFormatter(cacheData.amount, 2),
|
||||
});
|
||||
}
|
||||
}, [nftCache?.data, nftCache.isLoading, labelKey, timeRange]);
|
||||
if (nftCache.isLoading || !nftData) return "";
|
||||
|
||||
return (
|
||||
<Stack className={className}>
|
||||
<Stack className={className} ref={innerRef}>
|
||||
<Box
|
||||
id="nft-card-title"
|
||||
w="full"
|
||||
borderTopRadius="inherit"
|
||||
borderRadius="base"
|
||||
fontWeight="600"
|
||||
bgColor="gray.200"
|
||||
px={4}
|
||||
textAlign="center"
|
||||
fontSize={["sm", "md", null]}
|
||||
>
|
||||
{label}
|
||||
</Box>
|
||||
|
@ -122,12 +126,13 @@ const StatsCard_ = ({ className, label, netLabel, labelKey, timeRange }) => {
|
|||
>
|
||||
<Box
|
||||
w="100%"
|
||||
fontSize="1.125rem"
|
||||
fontSize={["1rem", "1.125rem", null]}
|
||||
borderStyle="dashed"
|
||||
borderRightWidth="3px"
|
||||
borderRightColor="gray.300"
|
||||
// alignItems="center"
|
||||
h="100%"
|
||||
id="nft-card-value"
|
||||
>
|
||||
<Link
|
||||
textDecorationLine="underline"
|
||||
|
@ -145,9 +150,10 @@ const StatsCard_ = ({ className, label, netLabel, labelKey, timeRange }) => {
|
|||
<Stack
|
||||
w="100%"
|
||||
direction="row"
|
||||
fontSize="1.125rem"
|
||||
fontSize={["1rem", "1.125rem", null]}
|
||||
placeContent="center"
|
||||
alignItems="center"
|
||||
id="nft-card-value-change"
|
||||
>
|
||||
{nftData.isValueIncrease && <TriangleUpIcon color="suggested.900" />}
|
||||
{!nftData.isValueIncrease && <TriangleDownIcon color="unsafe.900" />}
|
||||
|
@ -165,12 +171,18 @@ const StatsCard_ = ({ className, label, netLabel, labelKey, timeRange }) => {
|
|||
borderTopStyle="dashed"
|
||||
borderTopColor="gray.300"
|
||||
gridColumn="span 2"
|
||||
fontSize="0.825rem"
|
||||
fontSize={["0.625rem", "0.825rem", null]}
|
||||
id="nft-card-share-label"
|
||||
>
|
||||
Total share in {netLabel}
|
||||
</Text>
|
||||
<Text>{nftData.share}%</Text>
|
||||
<Stack direction="row" placeContent="center" alignItems="center">
|
||||
<Text id="nft-card-share-value">{nftData.share}%</Text>
|
||||
<Stack
|
||||
direction="row"
|
||||
placeContent="center"
|
||||
alignItems="center"
|
||||
id="nft-card-share-change"
|
||||
>
|
||||
{nftData.isShareIncrease && (
|
||||
<TriangleUpIcon color="suggested.900" />
|
||||
)}
|
||||
|
@ -195,7 +207,7 @@ const StatsCard_ = ({ className, label, netLabel, labelKey, timeRange }) => {
|
|||
const StatsCard = chakra(StatsCard_, {
|
||||
baseStyle: {
|
||||
borderStyle: "solid",
|
||||
borderRightWidth: "1px",
|
||||
// borderRightWidth: "1px",
|
||||
borderRightColor: "gray.600",
|
||||
w: "240px",
|
||||
minW: "240px",
|
||||
|
@ -204,4 +216,6 @@ const StatsCard = chakra(StatsCard_, {
|
|||
},
|
||||
});
|
||||
|
||||
export default StatsCard;
|
||||
export default React.forwardRef((props, ref) => (
|
||||
<StatsCard innerRef={ref} {...props} />
|
||||
));
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import { useState, useEffect } from "react";
|
||||
const useWindowSize = () => {
|
||||
const [windowSize, setWindowSize] = useState({
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
});
|
||||
useEffect(() => {
|
||||
// Handler to call on window resize
|
||||
function handleResize() {
|
||||
// Set window width/height to state
|
||||
setWindowSize({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
});
|
||||
}
|
||||
// Add event listener
|
||||
window.addEventListener("resize", handleResize);
|
||||
// Call handler right away so state gets updated with initial window size
|
||||
handleResize();
|
||||
// Remove event listener on cleanup
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
return windowSize;
|
||||
};
|
||||
|
||||
export default useWindowSize;
|
|
@ -63,7 +63,6 @@ const UIProvider = ({ children }) => {
|
|||
}
|
||||
}, [user]);
|
||||
|
||||
|
||||
// *********** Sidebar states **********************
|
||||
|
||||
// Whether sidebar should be visible at all or hidden
|
||||
|
|
Ładowanie…
Reference in New Issue