reworked homepage

This commit is contained in:
PhilReact 2025-02-27 21:14:41 +02:00
parent 519a0bb652
commit ae5ca83963
13 changed files with 1137 additions and 524 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,10 @@
import React from 'react'
import './barSpinner.css'
export const BarSpinner = ({width = '20px', color}) => {
return (
<div style={{
width,
color: color || 'green'
}} className="loader-bar"></div>
)
}

View File

@ -0,0 +1,19 @@
/* HTML: <div class="loader"></div> */
.loader-bar {
width: 45px;
aspect-ratio: .75;
--c:no-repeat linear-gradient(currentColor 0 0);
background:
var(--c) 0% 100%,
var(--c) 50% 100%,
var(--c) 100% 100%;
background-size: 20% 65%;
animation: l8 1s infinite linear;
}
@keyframes l8 {
16.67% {background-position: 0% 0% ,50% 100%,100% 100%}
33.33% {background-position: 0% 0% ,50% 0% ,100% 100%}
50% {background-position: 0% 0% ,50% 0% ,100% 0% }
66.67% {background-position: 0% 100%,50% 0% ,100% 0% }
83.33% {background-position: 0% 100%,50% 100%,100% 0% }
}

View File

@ -0,0 +1,101 @@
import { Box, ButtonBase, Typography } from "@mui/material";
import React from "react";
import ChatIcon from "@mui/icons-material/Chat";
import qTradeLogo from "../../assets/Icons/q-trade-logo.webp";
import AppsIcon from "@mui/icons-material/Apps";
import { executeEvent } from "../../utils/events";
export const Explore = ({setDesktopViewMode}) => {
return (
<Box
sx={{
display: "flex",
gap: "20px",
flexWrap: "wrap",
}}
>
<ButtonBase
sx={{
"&:hover": { backgroundColor: "secondary.main" },
transition: "all 0.1s ease-in-out",
padding: "5px",
borderRadius: "5px",
gap: "5px",
}}
onClick={async () => {
executeEvent("addTab", {
data: { service: "APP", name: "q-trade" },
});
executeEvent("open-apps-mode", {});
}}
>
<img
style={{
borderRadius: "50%",
height: '30px'
}}
src={qTradeLogo}
/>
<Typography
sx={{
fontSize: "1rem",
}}
>
Trade QORT
</Typography>
</ButtonBase>
<ButtonBase
sx={{
"&:hover": { backgroundColor: "secondary.main" },
transition: "all 0.1s ease-in-out",
padding: "5px",
borderRadius: "5px",
gap: "5px",
}}
onClick={()=> {
setDesktopViewMode('apps')
}}
>
<AppsIcon
sx={{
color: "white",
}}
/>
<Typography
sx={{
fontSize: "1rem",
}}
>
See Apps
</Typography>
</ButtonBase>
<ButtonBase
sx={{
"&:hover": { backgroundColor: "secondary.main" },
transition: "all 0.1s ease-in-out",
padding: "5px",
borderRadius: "5px",
gap: "5px",
}}
onClick={async () => {
executeEvent("openGroupMessage", {
from: "0" ,
});
}}
>
<ChatIcon
sx={{
color: "white",
}}
/>
<Typography
sx={{
fontSize: "1rem",
}}
>
General Chat
</Typography>
</ButtonBase>
</Box>
);
};

View File

@ -1299,11 +1299,11 @@ export const Group = ({
if (isLoadingOpenSectionFromNotification.current) return; if (isLoadingOpenSectionFromNotification.current) return;
const groupId = e.detail?.from; const groupId = e.detail?.from;
const findGroup = groups?.find((group) => +group?.groupId === +groupId); const findGroup = groups?.find((group) => +group?.groupId === +groupId);
if (findGroup?.groupId === selectedGroup?.groupId) { if (findGroup?.groupId === selectedGroup?.groupId) {
isLoadingOpenSectionFromNotification.current = false; isLoadingOpenSectionFromNotification.current = false;
setChatMode("groups");
setDesktopViewMode('chat')
return; return;
} }
if (findGroup) { if (findGroup) {

View File

@ -10,16 +10,20 @@ import CommentIcon from "@mui/icons-material/Comment";
import InfoIcon from "@mui/icons-material/Info"; import InfoIcon from "@mui/icons-material/Info";
import GroupAddIcon from "@mui/icons-material/GroupAdd"; import GroupAddIcon from "@mui/icons-material/GroupAdd";
import { executeEvent } from "../../utils/events"; import { executeEvent } from "../../utils/events";
import { Box, Typography } from "@mui/material"; import { Box, ButtonBase, Collapse, Typography } from "@mui/material";
import { Spacer } from "../../common/Spacer"; import { Spacer } from "../../common/Spacer";
import { getGroupNames } from "./UserListOfInvites"; import { getGroupNames } from "./UserListOfInvites";
import { CustomLoader } from "../../common/CustomLoader"; import { CustomLoader } from "../../common/CustomLoader";
import { getBaseApiReact, isMobile } from "../../App"; import { getBaseApiReact, isMobile } from "../../App";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
export const GroupInvites = ({ myAddress, setOpenAddGroup }) => { export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState( const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState(
[] []
); );
const [isExpanded, setIsExpanded] = React.useState(false);
const [loading, setLoading] = React.useState(true); const [loading, setLoading] = React.useState(true);
const getJoinRequests = async () => { const getJoinRequests = async () => {
@ -53,121 +57,129 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
alignItems: "center", alignItems: "center",
}} }}
> >
<Box <ButtonBase
sx={{ sx={{
width: "322px", width: "322px",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "row",
padding: "0px 20px", padding: "0px 20px",
gap: '10px',
justifyContent: 'flex-start'
}} }}
onClick={()=> setIsExpanded((prev)=> !prev)}
> >
<Typography <Typography
sx={{ sx={{
fontSize: "13px", fontSize: "1rem",
fontWeight: 600,
}} }}
> >
Group Invites: Group Invites {groupsWithJoinRequests?.length > 0 && ` (${groupsWithJoinRequests?.length})`}
</Typography> </Typography>
<Spacer height="10px" /> {isExpanded ? <ExpandLessIcon sx={{
</Box> marginLeft: 'auto'
}} /> : (
<ExpandMoreIcon sx={{
marginLeft: 'auto'
}}/>
)}
</ButtonBase>
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
<Box
sx={{
width: "322px",
height: isMobile ? "165px" : "250px",
<Box display: "flex",
sx={{ flexDirection: "column",
width: "322px", bgcolor: "background.paper",
height: isMobile ? "165px" : "250px", padding: "20px",
borderRadius: "19px",
display: "flex", }}
flexDirection: "column", >
bgcolor: "background.paper", {loading && groupsWithJoinRequests.length === 0 && (
padding: "20px", <Box
borderRadius: "19px", sx={{
}} width: "100%",
> display: "flex",
{loading && groupsWithJoinRequests.length === 0 && ( justifyContent: "center",
<Box }}
>
<CustomLoader />
</Box>
)}
{!loading && groupsWithJoinRequests.length === 0 && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100%",
}}
>
<Typography
sx={{
fontSize: "11px",
fontWeight: 400,
color: "rgba(255, 255, 255, 0.2)",
}}
>
Nothing to display
</Typography>
</Box>
)}
<List
sx={{ sx={{
width: "100%", width: "100%",
display: "flex", maxWidth: 360,
justifyContent: "center", bgcolor: "background.paper",
maxHeight: "300px",
overflow: "auto",
}} }}
className="scrollable-container"
> >
<CustomLoader /> {groupsWithJoinRequests?.map((group) => {
</Box> return (
)} <ListItem
{!loading && groupsWithJoinRequests.length === 0 && ( sx={{
<Box marginBottom: "20px",
sx={{ }}
width: "100%", key={group?.groupId}
display: "flex", onClick={() => {
justifyContent: "center", setOpenAddGroup(true);
alignItems: 'center', setTimeout(() => {
height: '100%', executeEvent("openGroupInvitesRequest", {});
}, 300);
}} }}
> disablePadding
<Typography secondaryAction={
sx={{ <IconButton edge="end" aria-label="comments">
fontSize: "11px", <GroupAddIcon
fontWeight: 400, sx={{
color: 'rgba(255, 255, 255, 0.2)' color: "white",
}} fontSize: "18px",
> }}
Nothing to display />
</Typography> </IconButton>
</Box> }
)} >
<List <ListItemButton disableRipple role={undefined} dense>
sx={{ <ListItemText
width: "100%",
maxWidth: 360,
bgcolor: "background.paper",
maxHeight: "300px",
overflow: "auto",
}}
className="scrollable-container"
>
{groupsWithJoinRequests?.map((group) => {
return (
<ListItem
sx={{
marginBottom: "20px",
}}
key={group?.groupId}
onClick={() => {
setOpenAddGroup(true);
setTimeout(() => {
executeEvent("openGroupInvitesRequest", {});
}, 300);
}}
disablePadding
secondaryAction={
<IconButton edge="end" aria-label="comments">
<GroupAddIcon
sx={{ sx={{
color: "white", "& .MuiTypography-root": {
fontSize: "18px", fontSize: "13px",
fontWeight: 400,
},
}} }}
primary={`${group?.groupName} has invited you`}
/> />
</IconButton> </ListItemButton>
} </ListItem>
> );
<ListItemButton disableRipple role={undefined} dense> })}
<ListItemText </List>
sx={{ </Box>
"& .MuiTypography-root": { </Collapse>
fontSize: "13px",
fontWeight: 400,
},
}}
primary={`${group?.groupName} has invited you`}
/>
</ListItemButton>
</ListItem>
);
})}
</List>
</Box>
</Box> </Box>
); );
}; };

View File

@ -11,16 +11,20 @@ import InfoIcon from "@mui/icons-material/Info";
import { RequestQueueWithPromise } from "../../utils/queue/queue"; import { RequestQueueWithPromise } from "../../utils/queue/queue";
import GroupAddIcon from '@mui/icons-material/GroupAdd'; import GroupAddIcon from '@mui/icons-material/GroupAdd';
import { executeEvent } from "../../utils/events"; import { executeEvent } from "../../utils/events";
import { Box, Typography } from "@mui/material"; import { Box, ButtonBase, Collapse, Typography } from "@mui/material";
import { Spacer } from "../../common/Spacer"; import { Spacer } from "../../common/Spacer";
import { CustomLoader } from "../../common/CustomLoader"; import { CustomLoader } from "../../common/CustomLoader";
import { getBaseApi } from "../../background"; import { getBaseApi } from "../../background";
import { MyContext, getBaseApiReact, isMobile } from "../../App"; import { MyContext, getBaseApiReact, isMobile } from "../../App";
import { myGroupsWhereIAmAdminAtom } from "../../atoms/global"; import { myGroupsWhereIAmAdminAtom } from "../../atoms/global";
import { useSetRecoilState } from "recoil"; import { useSetRecoilState } from "recoil";
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
export const requestQueueGroupJoinRequests = new RequestQueueWithPromise(2) export const requestQueueGroupJoinRequests = new RequestQueueWithPromise(2)
export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, getTimestampEnterChat, setSelectedGroup, setGroupSection, setMobileViewMode, setDesktopViewMode }) => { export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, getTimestampEnterChat, setSelectedGroup, setGroupSection, setMobileViewMode, setDesktopViewMode }) => {
const [isExpanded, setIsExpanded] = React.useState(false)
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState([]) const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState([])
const [loading, setLoading] = React.useState(true) const [loading, setLoading] = React.useState(true)
const {txList, setTxList} = React.useContext(MyContext) const {txList, setTxList} = React.useContext(MyContext)
@ -109,26 +113,33 @@ export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, get
flexDirection: "column", flexDirection: "column",
alignItems: 'center' alignItems: 'center'
}}> }}>
<Box <ButtonBase
sx={{ sx={{
width: "322px", width: "322px",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "row",
padding: '0px 20px', padding: '0px 20px',
gap: '10px',
justifyContent: 'flex-start'
}} }}
onClick={()=> setIsExpanded((prev)=> !prev)}
> >
<Typography <Typography
sx={{ sx={{
fontSize: "13px", fontSize: "1rem",
fontWeight: 600,
}} }}
> >
Join Requests: Join Requests {filteredJoinRequests?.filter((group)=> group?.data?.length > 0)?.length > 0 && ` (${filteredJoinRequests?.filter((group)=> group?.data?.length > 0)?.length})`}
</Typography> </Typography>
<Spacer height="10px" /> {isExpanded ? <ExpandLessIcon sx={{
</Box> marginLeft: 'auto'
}} /> : (
<ExpandMoreIcon sx={{
marginLeft: 'auto'
}}/>
)}
</ButtonBase>
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
<Box <Box
sx={{ sx={{
width: "322px", width: "322px",
@ -227,6 +238,7 @@ export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, get
</List> </List>
</Box> </Box>
</Collapse>
</Box> </Box>
); );
}; };

View File

@ -1,4 +1,4 @@
import { Box, Button, Typography } from "@mui/material"; import { Box, Button, Divider, Typography } from "@mui/material";
import React from "react"; import React from "react";
import { Spacer } from "../../common/Spacer"; import { Spacer } from "../../common/Spacer";
import { ListOfThreadPostsWatched } from "./ListOfThreadPostsWatched"; import { ListOfThreadPostsWatched } from "./ListOfThreadPostsWatched";
@ -7,7 +7,10 @@ import { GroupJoinRequests } from "./GroupJoinRequests";
import { GroupInvites } from "./GroupInvites"; import { GroupInvites } from "./GroupInvites";
import RefreshIcon from "@mui/icons-material/Refresh"; import RefreshIcon from "@mui/icons-material/Refresh";
import { ListOfGroupPromotions } from "./ListOfGroupPromotions"; import { ListOfGroupPromotions } from "./ListOfGroupPromotions";
import { QortPrice } from "../Home/QortPrice";
import ExploreIcon from "@mui/icons-material/Explore";
import { Explore } from "../Explore/Explore";
import { NewUsersCTA } from "../Home/NewUsersCTA";
export const HomeDesktop = ({ export const HomeDesktop = ({
refreshHomeDataFunc, refreshHomeDataFunc,
myAddress, myAddress,
@ -22,12 +25,12 @@ export const HomeDesktop = ({
setOpenAddGroup, setOpenAddGroup,
setMobileViewMode, setMobileViewMode,
setDesktopViewMode, setDesktopViewMode,
desktopViewMode desktopViewMode,
}) => { }) => {
return ( return (
<Box <Box
sx={{ sx={{
display: desktopViewMode === 'home' ? "flex" : "none", display: desktopViewMode === "home" ? "flex" : "none",
width: "100%", width: "100%",
flexDirection: "column", flexDirection: "column",
height: "100%", height: "100%",
@ -36,105 +39,169 @@ export const HomeDesktop = ({
}} }}
> >
<Spacer height="20px" /> <Spacer height="20px" />
<Box sx={{ <Box
display: "flex",
width: "100%",
flexDirection: "column",
height: "100%",
alignItems: "flex-start",
maxWidth: '1036px'
}}>
<Typography
sx={{ sx={{
color: "rgba(255, 255, 255, 1)", display: "flex",
fontWeight: 400, width: "100%",
fontSize: userInfo?.name?.length > 15 ? "16px" : "20px", flexDirection: "column",
padding: '10px' height: "100%",
alignItems: "flex-start",
maxWidth: "1036px",
}} }}
> >
Welcome <Typography
{userInfo?.name ? (
<span
style={{
fontStyle: "italic",
}}
>{`, ${userInfo?.name}`}</span>
) : null}
</Typography>
<Spacer height="30px" />
{!isLoadingGroups && (
<Box
sx={{ sx={{
display: "flex", color: "rgba(255, 255, 255, 1)",
gap: "15px", fontWeight: 400,
flexWrap: "wrap", fontSize: userInfo?.name?.length > 15 ? "16px" : "20px",
justifyContent: "center", padding: "10px",
}} }}
> >
<Box sx={{ Welcome
width: '330px', {userInfo?.name ? (
display: 'flex', <span
alignItems: 'center', style={{
justifyContent: 'center' fontStyle: "italic",
}}> }}
<ThingsToDoInitial >{`, ${userInfo?.name}`}</span>
balance={balance} ) : null}
myAddress={myAddress} </Typography>
name={userInfo?.name} <Spacer height="30px" />
userInfo={userInfo} {!isLoadingGroups && (
hasGroups={groups?.length !== 0} <Box
/> sx={{
</Box> display: "flex",
{desktopViewMode === 'home' && ( gap: "20px",
<> flexWrap: "wrap",
<Box sx={{ width: "100%",
justifyContent: "center",
}}
>
<Box
sx={{
display: "flex",
gap: "20px",
flexWrap: "wrap",
flexDirection: "column",
}}
>
<Box
sx={{
width: "330px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<ThingsToDoInitial
balance={balance}
myAddress={myAddress}
name={userInfo?.name}
userInfo={userInfo}
hasGroups={
groups?.filter((item) => item?.groupId !== "0").length !== 0
}
/>
</Box>
{desktopViewMode === "home" && (
<>
{/* <Box sx={{
width: '330px', width: '330px',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center'
}}> }}>
<ListOfThreadPostsWatched /> <ListOfThreadPostsWatched />
</Box> */}
<Box
sx={{
width: "330px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<GroupJoinRequests
setGroupSection={setGroupSection}
setSelectedGroup={setSelectedGroup}
getTimestampEnterChat={getTimestampEnterChat}
setOpenManageMembers={setOpenManageMembers}
myAddress={myAddress}
groups={groups}
setMobileViewMode={setMobileViewMode}
setDesktopViewMode={setDesktopViewMode}
/>
</Box>
<Box
sx={{
width: "330px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<GroupInvites
setOpenAddGroup={setOpenAddGroup}
myAddress={myAddress}
groups={groups}
setMobileViewMode={setMobileViewMode}
/>
</Box>
</>
)}
</Box>
<QortPrice />
</Box> </Box>
<Box sx={{ )}
width: '330px',
display: 'flex', {!isLoadingGroups && (
alignItems: 'center', <>
justifyContent: 'center' <Spacer height="60px" />
}}> <Divider
<GroupJoinRequests color="secondary"
setGroupSection={setGroupSection} sx={{
setSelectedGroup={setSelectedGroup} width: "100%",
getTimestampEnterChat={getTimestampEnterChat} }}
setOpenManageMembers={setOpenManageMembers} >
myAddress={myAddress} <Box
groups={groups} sx={{
setMobileViewMode={setMobileViewMode} display: "flex",
setDesktopViewMode={setDesktopViewMode} gap: "10px",
/> alignItems: "center",
}}
>
<ExploreIcon
sx={{
color: "white",
}}
/>{" "}
<Typography
sx={{
fontSize: "1rem",
}}
>
Explore
</Typography>{" "}
</Box> </Box>
<Box sx={{ </Divider>
width: '330px', <Box
display: 'flex', sx={{
alignItems: 'center', display: "flex",
justifyContent: 'center' gap: "20px",
}}> flexWrap: "wrap",
<GroupInvites width: "100%",
setOpenAddGroup={setOpenAddGroup} justifyContent: "center",
myAddress={myAddress} }}
groups={groups} >
setMobileViewMode={setMobileViewMode} <ListOfGroupPromotions />
/> <Explore setDesktopViewMode={setDesktopViewMode} />
</Box> </Box>
</> <NewUsersCTA balance={balance} />
)} </>
</Box> )}
)}
{!isLoadingGroups && (
<ListOfGroupPromotions />
)}
</Box> </Box>
<Spacer height="26px" /> <Spacer height="26px" />
{/* <Box {/* <Box
@ -155,7 +222,7 @@ export const HomeDesktop = ({
Refresh home data Refresh home data
</Button> </Button>
</Box> */} </Box> */}
<Spacer height="180px" /> <Spacer height="180px" />
</Box> </Box>
); );

View File

@ -9,6 +9,8 @@ import {
Avatar, Avatar,
Box, Box,
Button, Button,
ButtonBase,
Collapse,
Dialog, Dialog,
DialogActions, DialogActions,
DialogContent, DialogContent,
@ -28,8 +30,8 @@ import {
import { getNameInfo } from "./Group"; import { getNameInfo } from "./Group";
import { getBaseApi, getFee } from "../../background"; import { getBaseApi, getFee } from "../../background";
import { LoadingButton } from "@mui/lab"; import { LoadingButton } from "@mui/lab";
import LockIcon from '@mui/icons-material/Lock'; import LockIcon from "@mui/icons-material/Lock";
import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred'; import NoEncryptionGmailerrorredIcon from "@mui/icons-material/NoEncryptionGmailerrorred";
import { import {
MyContext, MyContext,
getArbitraryEndpointReact, getArbitraryEndpointReact,
@ -40,7 +42,11 @@ import { Spacer } from "../../common/Spacer";
import { CustomLoader } from "../../common/CustomLoader"; import { CustomLoader } from "../../common/CustomLoader";
import { RequestQueueWithPromise } from "../../utils/queue/queue"; import { RequestQueueWithPromise } from "../../utils/queue/queue";
import { useRecoilState } from "recoil"; import { useRecoilState } from "recoil";
import { myGroupsWhereIAmAdminAtom, promotionTimeIntervalAtom, promotionsAtom } from "../../atoms/global"; import {
myGroupsWhereIAmAdminAtom,
promotionTimeIntervalAtom,
promotionsAtom,
} from "../../atoms/global";
import { Label } from "./AddGroup"; import { Label } from "./AddGroup";
import ShortUniqueId from "short-unique-id"; import ShortUniqueId from "short-unique-id";
import { CustomizedSnackbars } from "../Snackbar/Snackbar"; import { CustomizedSnackbars } from "../Snackbar/Snackbar";
@ -48,7 +54,8 @@ import { getGroupNames } from "./UserListOfInvites";
import { WrapperUserAction } from "../WrapperUserAction"; import { WrapperUserAction } from "../WrapperUserAction";
import { useVirtualizer } from "@tanstack/react-virtual"; import { useVirtualizer } from "@tanstack/react-virtual";
import ErrorBoundary from "../../common/ErrorBoundary"; import ErrorBoundary from "../../common/ErrorBoundary";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
export const requestQueuePromos = new RequestQueueWithPromise(20); export const requestQueuePromos = new RequestQueueWithPromise(20);
export function utf8ToBase64(inputString: string): string { export function utf8ToBase64(inputString: string): string {
@ -65,8 +72,6 @@ export function utf8ToBase64(inputString: string): string {
const uid = new ShortUniqueId({ length: 8 }); const uid = new ShortUniqueId({ length: 8 });
export function getGroupId(str) { export function getGroupId(str) {
const match = str.match(/group-(\d+)-/); const match = str.match(/group-(\d+)-/);
return match ? match[1] : null; return match ? match[1] : null;
@ -82,12 +87,12 @@ export const ListOfGroupPromotions = () => {
const [myGroupsWhereIAmAdmin, setMyGroupsWhereIAmAdmin] = useRecoilState( const [myGroupsWhereIAmAdmin, setMyGroupsWhereIAmAdmin] = useRecoilState(
myGroupsWhereIAmAdminAtom myGroupsWhereIAmAdminAtom
); );
const [promotions, setPromotions] = useRecoilState( const [promotions, setPromotions] = useRecoilState(promotionsAtom);
promotionsAtom
);
const [promotionTimeInterval, setPromotionTimeInterval] = useRecoilState( const [promotionTimeInterval, setPromotionTimeInterval] = useRecoilState(
promotionTimeIntervalAtom promotionTimeIntervalAtom
); );
const [isExpanded, setIsExpanded] = React.useState(false);
const [openSnack, setOpenSnack] = useState(false); const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = useState(null); const [infoSnack, setInfoSnack] = useState(null);
const [fee, setFee] = useState(null); const [fee, setFee] = useState(null);
@ -96,16 +101,16 @@ export const ListOfGroupPromotions = () => {
const { show, setTxList } = useContext(MyContext); const { show, setTxList } = useContext(MyContext);
const listRef = useRef(); const listRef = useRef();
const rowVirtualizer = useVirtualizer({ const rowVirtualizer = useVirtualizer({
count: promotions.length, count: promotions.length,
getItemKey: React.useCallback( getItemKey: React.useCallback(
(index) => promotions[index]?.identifier, (index) => promotions[index]?.identifier,
[promotions] [promotions]
), ),
getScrollElement: () => listRef.current, getScrollElement: () => listRef.current,
estimateSize: () => 80, // Provide an estimated height of items, adjust this as needed estimateSize: () => 80, // Provide an estimated height of items, adjust this as needed
overscan: 10, // Number of items to render outside the visible area to improve smoothness overscan: 10, // Number of items to render outside the visible area to improve smoothness
}); });
useEffect(() => { useEffect(() => {
try { try {
@ -117,7 +122,7 @@ export const ListOfGroupPromotions = () => {
}, []); }, []);
const getPromotions = useCallback(async () => { const getPromotions = useCallback(async () => {
try { try {
setPromotionTimeInterval(Date.now()) setPromotionTimeInterval(Date.now());
const identifier = `group-promotions-ui24-`; const identifier = `group-promotions-ui24-`;
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=100&includemetadata=false&reverse=true&prefix=true`; const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=100&includemetadata=false&reverse=true&prefix=true`;
const response = await fetch(url, { const response = await fetch(url, {
@ -168,7 +173,9 @@ export const ListOfGroupPromotions = () => {
}); });
await Promise.all(getPromos); await Promise.all(getPromos);
const groupWithInfo = await getGroupNames(data.sort((a, b) => b.created - a.created)); const groupWithInfo = await getGroupNames(
data.sort((a, b) => b.created - a.created)
);
setPromotions(groupWithInfo); setPromotions(groupWithInfo);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -177,22 +184,23 @@ export const ListOfGroupPromotions = () => {
useEffect(() => { useEffect(() => {
const now = Date.now(); const now = Date.now();
const timeSinceLastFetch = now - promotionTimeInterval; const timeSinceLastFetch = now - promotionTimeInterval;
const initialDelay = timeSinceLastFetch >= THIRTY_MINUTES const initialDelay =
? 0 timeSinceLastFetch >= THIRTY_MINUTES
: THIRTY_MINUTES - timeSinceLastFetch; ? 0
: THIRTY_MINUTES - timeSinceLastFetch;
const initialTimeout = setTimeout(() => { const initialTimeout = setTimeout(() => {
getPromotions(); getPromotions();
// Start a 30-minute interval // Start a 30-minute interval
const interval = setInterval(() => { const interval = setInterval(() => {
getPromotions(); getPromotions();
}, THIRTY_MINUTES); }, THIRTY_MINUTES);
return () => clearInterval(interval); return () => clearInterval(interval);
}, initialDelay); }, initialDelay);
return () => clearTimeout(initialTimeout); return () => clearTimeout(initialTimeout);
}, [getPromotions, promotionTimeInterval]); }, [getPromotions, promotionTimeInterval]);
@ -328,100 +336,143 @@ export const ListOfGroupPromotions = () => {
} }
}; };
return ( return (
<Box <Box
sx={{ sx={{
width: "100%", width: "100%",
display: "flex", display: "flex",
marginTop: "20px",
flexDirection: "column", flexDirection: "column",
alignItems: "center", alignItems: "center",
marginTop: "25px", justifyContent: "center",
}} }}
> >
<Box <Box sx={{
sx={{ display: 'flex',
width: isMobile ? "320px" : "750px", gap: '20px',
maxWidth: "90%", width: '100%',
display: "flex", justifyContent: 'space-between'
flexDirection: "column", }}>
padding: "0px 20px", <ButtonBase
}}
>
<Box
sx={{ sx={{
width: "100%",
display: "flex", display: "flex",
justifyContent: "space-between", flexDirection: "row",
alignItems: "center", padding: `0px ${isExpanded ? "24px" : "20px"}`,
gap: "10px",
justifyContent: "flex-start",
alignSelf: isExpanded && "flex-start",
}} }}
onClick={() => setIsExpanded((prev) => !prev)}
> >
<Typography <Typography
sx={{ sx={{
fontSize: "13px", fontSize: "1rem",
fontWeight: 600,
}} }}
> >
Group Promotions Group promotions {promotions.length > 0 && ` (${promotions.length})`}
</Typography> </Typography>
<Button {isExpanded ? (
variant="contained" <ExpandLessIcon
onClick={() => setIsShowModal(true)} sx={{
sx={{ marginLeft: "auto",
fontSize: "12px", }}
}} />
> ) : (
Add Promotion <ExpandMoreIcon
</Button> sx={{
</Box> marginLeft: "auto",
<Spacer height="10px" /> }}
/>
)}
</ButtonBase>
<Box
style={{
width: "330px",
}}
/>
</Box> </Box>
<Box <Collapse in={isExpanded} timeout="auto" unmountOnExit>
sx={{ <>
width: isMobile ? "320px" : "750px",
maxWidth: "90%",
maxHeight: "700px",
display: "flex",
flexDirection: "column",
bgcolor: "background.paper",
padding: "20px 0px",
borderRadius: "19px",
}}
>
{loading && promotions.length === 0 && (
<Box <Box
sx={{ sx={{
width: "100%", width: isMobile ? "320px" : "750px",
maxWidth: "90%",
display: "flex", display: "flex",
justifyContent: "center", flexDirection: "column",
padding: "0px 20px",
}} }}
> >
<CustomLoader /> <Box
</Box>
)}
{!loading && promotions.length === 0 && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100%",
}}
>
<Typography
sx={{ sx={{
fontSize: "11px", width: "100%",
fontWeight: 400, display: "flex",
color: "rgba(255, 255, 255, 0.2)", justifyContent: "space-between",
alignItems: "center",
}} }}
> >
Nothing to display <Typography
</Typography> sx={{
fontSize: "13px",
fontWeight: 600,
}}
></Typography>
<Button
variant="contained"
onClick={() => setIsShowModal(true)}
sx={{
fontSize: "12px",
}}
>
Add Promotion
</Button>
</Box>
<Spacer height="10px" />
</Box> </Box>
)} <Box
sx={{
width: isMobile ? "320px" : "750px",
maxWidth: "90%",
maxHeight: "700px",
display: "flex",
flexDirection: "column",
bgcolor: "background.paper",
padding: "20px 0px",
borderRadius: "19px",
}}
>
{loading && promotions.length === 0 && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
}}
>
<CustomLoader />
</Box>
)}
{!loading && promotions.length === 0 && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100%",
}}
>
<Typography
sx={{
fontSize: "11px",
fontWeight: 400,
color: "rgba(255, 255, 255, 0.2)",
}}
>
Nothing to display
</Typography>
</Box>
)}
<div <div
style={{ style={{
height: "600px", height: "600px",
@ -460,7 +511,6 @@ export const ListOfGroupPromotions = () => {
const index = virtualRow.index; const index = virtualRow.index;
const promotion = promotions[index]; const promotion = promotions[index];
return ( return (
<div <div
data-index={virtualRow.index} //needed for dynamic row height measurement data-index={virtualRow.index} //needed for dynamic row height measurement
ref={rowVirtualizer.measureElement} //measure dynamic row height ref={rowVirtualizer.measureElement} //measure dynamic row height
@ -479,236 +529,251 @@ export const ListOfGroupPromotions = () => {
gap: "5px", gap: "5px",
}} }}
> >
<ErrorBoundary <ErrorBoundary
fallback={ fallback={
<Typography> <Typography>
Error loading content: Invalid Data Error loading content: Invalid Data
</Typography> </Typography>
} }
> >
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
width: "100%", width: "100%",
padding: "0px 20px", padding: "0px 20px",
}} }}
> >
<Popover <Popover
open={openPopoverIndex === promotion?.groupId} open={openPopoverIndex === promotion?.groupId}
anchorEl={popoverAnchor} anchorEl={popoverAnchor}
onClose={(event, reason) => { onClose={(event, reason) => {
if (reason === "backdropClick") { if (reason === "backdropClick") {
// Prevent closing on backdrop click // Prevent closing on backdrop click
return; return;
} }
handlePopoverClose(); // Close only on other events like Esc key press handlePopoverClose(); // Close only on other events like Esc key press
}} }}
anchorOrigin={{ anchorOrigin={{
vertical: "top", vertical: "top",
horizontal: "center", horizontal: "center",
}} }}
transformOrigin={{ transformOrigin={{
vertical: "bottom", vertical: "bottom",
horizontal: "center", horizontal: "center",
}} }}
style={{ marginTop: "8px" }} style={{ marginTop: "8px" }}
> >
<Box <Box
sx={{ sx={{
width: "325px", width: "325px",
height: "auto", height: "auto",
maxHeight: "400px", maxHeight: "400px",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
alignItems: "center", alignItems: "center",
gap: "10px", gap: "10px",
padding: "10px", padding: "10px",
}} }}
> >
<Typography <Typography
sx={{ sx={{
fontSize: "13px", fontSize: "13px",
fontWeight: 600, fontWeight: 600,
}} }}
> >
Group name: {` ${promotion?.groupName}`} Group name: {` ${promotion?.groupName}`}
</Typography> </Typography>
<Typography <Typography
sx={{ sx={{
fontSize: "13px", fontSize: "13px",
fontWeight: 600, fontWeight: 600,
}} }}
> >
Number of members: {` ${promotion?.memberCount}`} Number of members:{" "}
</Typography> {` ${promotion?.memberCount}`}
{promotion?.description && ( </Typography>
<Typography {promotion?.description && (
sx={{ <Typography
fontSize: "13px", sx={{
fontWeight: 600, fontSize: "13px",
}} fontWeight: 600,
> }}
{promotion?.description} >
</Typography> {promotion?.description}
)} </Typography>
{promotion?.isOpen === false && ( )}
<Typography {promotion?.isOpen === false && (
sx={{ <Typography
fontSize: "13px", sx={{
fontWeight: 600, fontSize: "13px",
}} fontWeight: 600,
> }}
*This is a closed/private group, so you will need to wait >
until an admin accepts your request *This is a closed/private group, so you
</Typography> will need to wait until an admin accepts
)} your request
<Spacer height="5px" /> </Typography>
<Box )}
sx={{ <Spacer height="5px" />
display: "flex", <Box
gap: "20px", sx={{
alignItems: "center", display: "flex",
width: "100%", gap: "20px",
justifyContent: "center", alignItems: "center",
}} width: "100%",
> justifyContent: "center",
<LoadingButton }}
loading={isLoadingJoinGroup} >
loadingPosition="start" <LoadingButton
variant="contained" loading={isLoadingJoinGroup}
onClick={handlePopoverClose} loadingPosition="start"
> variant="contained"
Close onClick={handlePopoverClose}
</LoadingButton> >
<LoadingButton Close
loading={isLoadingJoinGroup} </LoadingButton>
loadingPosition="start" <LoadingButton
variant="contained" loading={isLoadingJoinGroup}
onClick={() => loadingPosition="start"
handleJoinGroup(promotion, promotion?.isOpen) variant="contained"
} onClick={() =>
> handleJoinGroup(
Join promotion,
</LoadingButton> promotion?.isOpen
</Box> )
</Box> }
</Popover> >
Join
</LoadingButton>
</Box>
</Box>
</Popover>
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
width: "100%", width: "100%",
}} }}
> >
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: "15px", gap: "15px",
}} }}
> >
<Avatar <Avatar
sx={{ sx={{
backgroundColor: "#27282c", backgroundColor: "#27282c",
color: "white", color: "white",
}} }}
alt={promotion?.name} alt={promotion?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${ src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
promotion?.name promotion?.name
}/qortal_avatar?async=true`} }/qortal_avatar?async=true`}
> >
{promotion?.name?.charAt(0)} {promotion?.name?.charAt(0)}
</Avatar> </Avatar>
<Typography <Typography
sx={{ sx={{
fontWight: 600, fontWight: 600,
fontFamily: "Inter", fontFamily: "Inter",
color: "cadetBlue", color: "cadetBlue",
}} }}
> >
{promotion?.name} {promotion?.name}
</Typography> </Typography>
</Box> </Box>
<Typography <Typography
sx={{ sx={{
fontWight: 600, fontWight: 600,
fontFamily: "Inter", fontFamily: "Inter",
color: "cadetBlue", color: "cadetBlue",
}} }}
> >
{promotion?.groupName} {promotion?.groupName}
</Typography> </Typography>
</Box> </Box>
<Spacer height="20px" /> <Spacer height="20px" />
<Box sx={{ <Box
display: 'flex', sx={{
gap: '20px', display: "flex",
alignItems: 'center' gap: "20px",
}}> alignItems: "center",
{promotion?.isOpen === false && ( }}
<LockIcon sx={{ >
color: 'var(--green)' {promotion?.isOpen === false && (
}} /> <LockIcon
)} sx={{
{promotion?.isOpen === true && ( color: "var(--green)",
<NoEncryptionGmailerrorredIcon sx={{ }}
color: 'var(--danger)' />
}} /> )}
)} {promotion?.isOpen === true && (
<Typography <NoEncryptionGmailerrorredIcon
sx={{ sx={{
fontSize: "15px", color: "var(--danger)",
fontWeight: 600, }}
}} />
> )}
{promotion?.isOpen ? 'Public group' : 'Private group' } <Typography
</Typography> sx={{
</Box> fontSize: "15px",
<Spacer height="20px" /> fontWeight: 600,
<Typography }}
sx={{ >
fontWight: 600, {promotion?.isOpen
fontFamily: "Inter", ? "Public group"
color: "cadetBlue", : "Private group"}
}} </Typography>
> </Box>
{promotion?.data} <Spacer height="20px" />
</Typography> <Typography
<Spacer height="20px" /> sx={{
<Box fontWight: 600,
sx={{ fontFamily: "Inter",
display: "flex", color: "cadetBlue",
justifyContent: "center", }}
width: "100%", >
}} {promotion?.data}
> </Typography>
<Button <Spacer height="20px" />
// variant="contained" <Box
onClick={(event) => handlePopoverOpen(event, promotion?.groupId)} sx={{
sx={{ display: "flex",
fontSize: "12px", justifyContent: "center",
color: 'white' width: "100%",
}} }}
> >
Join Group: {` ${promotion?.groupName}`} <Button
</Button> // variant="contained"
</Box> onClick={(event) =>
</Box> handlePopoverOpen(event, promotion?.groupId)
<Spacer height="50px" /> }
sx={{
fontSize: "12px",
color: "white",
}}
>
Join Group: {` ${promotion?.groupName}`}
</Button>
</Box>
</Box>
<Spacer height="50px" />
</ErrorBoundary> </ErrorBoundary>
</div> </div>
); );
})} })}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</Box>
</Box> </>
</Collapse>
<Spacer height="20px" /> <Spacer height="20px" />
{isShowModal && ( {isShowModal && (

View File

@ -1,11 +1,11 @@
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useMemo, useState } from 'react'
import List from "@mui/material/List"; import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem"; import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton"; import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText"; import ListItemText from "@mui/material/ListItemText";
import moment from 'moment' import moment from 'moment'
import { Box, Typography } from "@mui/material"; import { Box, ButtonBase, Collapse, Typography } from "@mui/material";
import { Spacer } from "../../common/Spacer"; import { Spacer } from "../../common/Spacer";
import { getBaseApiReact, isMobile } from "../../App"; import { getBaseApiReact, isMobile } from "../../App";
import { MessagingIcon } from '../../assets/Icons/MessagingIcon'; import { MessagingIcon } from '../../assets/Icons/MessagingIcon';
@ -15,6 +15,10 @@ import { executeEvent } from '../../utils/events';
import { CustomLoader } from '../../common/CustomLoader'; import { CustomLoader } from '../../common/CustomLoader';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { mailsAtom, qMailLastEnteredTimestampAtom } from '../../atoms/global'; import { mailsAtom, qMailLastEnteredTimestampAtom } from '../../atoms/global';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import MarkEmailUnreadIcon from '@mui/icons-material/MarkEmailUnread';
import { last } from 'slate';
export const isLessThanOneWeekOld = (timestamp) => { export const isLessThanOneWeekOld = (timestamp) => {
// Current time in milliseconds // Current time in milliseconds
const now = Date.now(); const now = Date.now();
@ -41,6 +45,7 @@ export function formatEmailDate(timestamp: number) {
} }
} }
export const QMailMessages = ({userName, userAddress}) => { export const QMailMessages = ({userName, userAddress}) => {
const [isExpanded, setIsExpanded] = useState(false)
const [mails, setMails] = useRecoilState(mailsAtom) const [mails, setMails] = useRecoilState(mailsAtom)
const [lastEnteredTimestamp, setLastEnteredTimestamp] = useRecoilState(qMailLastEnteredTimestampAtom) const [lastEnteredTimestamp, setLastEnteredTimestamp] = useRecoilState(qMailLastEnteredTimestampAtom)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
@ -99,7 +104,16 @@ export const QMailMessages = ({userName, userAddress}) => {
}, [getMails, userName, userAddress]); }, [getMails, userName, userAddress]);
const anyUnread = useMemo(()=> {
let unread = false
mails.forEach((mail)=> {
if(lastEnteredTimestamp && isLessThanOneWeekOld(mail?.created)){
unread = true
}
})
return unread
}, [mails, lastEnteredTimestamp])
return ( return (
<Box <Box
@ -111,25 +125,37 @@ export const QMailMessages = ({userName, userAddress}) => {
}} }}
> >
<Box <ButtonBase
sx={{ sx={{
width: "322px", width: "322px",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "row",
gap: '10px',
padding: "0px 20px", padding: "0px 20px",
justifyContent: 'flex-start'
}} }}
onClick={()=> setIsExpanded((prev)=> !prev)}
> >
<Typography <Typography
sx={{ sx={{
fontSize: "13px", fontSize: "1rem",
fontWeight: 600,
}} }}
> >
Latest Q-Mails Latest Q-Mails
</Typography> </Typography>
<Spacer height="10px" /> <MarkEmailUnreadIcon sx={{
</Box> color: anyUnread ? '--unread' : 'white'
}}/>
{isExpanded ? <ExpandLessIcon sx={{
marginLeft: 'auto'
}} /> : (
<ExpandMoreIcon sx={{
color: anyUnread ? '--unread' : 'white',
marginLeft: 'auto'
}} />
)}
</ButtonBase>
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
<Box <Box
className="scrollable-container" className="scrollable-container"
sx={{ sx={{
@ -248,6 +274,7 @@ export const QMailMessages = ({userName, userAddress}) => {
</Box> </Box>
</Collapse>
</Box> </Box>
) )
} }

View File

@ -64,6 +64,7 @@ if(hasDoneNameAndBalanceAndIsLoaded){
<QMailMessages userAddress={userInfo?.address} userName={userInfo?.name} /> <QMailMessages userAddress={userInfo?.address} userName={userInfo?.name} />
); );
} }
if(!isLoaded) return null
return ( return (
<Box <Box
@ -84,7 +85,7 @@ if(hasDoneNameAndBalanceAndIsLoaded){
> >
<Typography <Typography
sx={{ sx={{
fontSize: "13px", fontSize: "1rem",
fontWeight: 600, fontWeight: 600,
}} }}
> >
@ -96,7 +97,6 @@ if(hasDoneNameAndBalanceAndIsLoaded){
<Box <Box
sx={{ sx={{
width: "322px", width: "322px",
height: isMobile ? "165px" : "250px",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
bgcolor: "background.paper", bgcolor: "background.paper",

View File

@ -0,0 +1,91 @@
import { Box, ButtonBase, Typography } from "@mui/material";
import React from "react";
import { Spacer } from "../../common/Spacer";
export const NewUsersCTA = ({ balance }) => {
if (balance === undefined || +balance > 0) return null;
return (
<Box
sx={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Spacer height="40px" />
<Box
sx={{
width: "320px",
justifyContent: "center",
flexDirection: "column",
alignItems: "center",
padding: "15px",
outline: "1px solid gray",
borderRadius: "4px",
}}
>
<Typography
sx={{
textAlign: "center",
fontSize: "1.2rem",
fontWeight: "bold",
}}
>
Are you a new user?
</Typography>
<Spacer height="20px" />
<Typography>
Please message us on Telegram or Discord if you need 4 QORT to start
chatting without any limitations
</Typography>
<Spacer height="20px" />
<Box
sx={{
width: "100%",
display: "flex",
gap: "10px",
justifyContent: "center",
}}
>
<ButtonBase
sx={{
textDecoration: "underline",
}}
onClick={() => {
if (window?.electronAPI?.openExternal) {
window.electronAPI.openExternal(
"https://link.qortal.dev/telegram-invite"
);
} else {
window.open(
"https://link.qortal.dev/telegram-invite",
"_blank"
);
}
}}
>
Telegram
</ButtonBase>
<ButtonBase
sx={{
textDecoration: "underline",
}}
onClick={() => {
if (window?.electronAPI?.openExternal) {
window.electronAPI.openExternal(
"https://link.qortal.dev/discord-invite"
);
} else {
window.open("https://link.qortal.dev/discord-invite", "_blank");
}
}}
>
Discord
</ButtonBase>
</Box>
</Box>
</Box>
);
};

View File

@ -0,0 +1,209 @@
import React, { useCallback, useEffect, useState } from 'react'
import { getBaseApiReact } from '../../App';
import { Box, Tooltip, Typography } from '@mui/material';
import { BarSpinner } from '../../common/Spinners/BarSpinner/BarSpinner';
function getAverageLtcPerQort(trades) {
let totalQort = 0;
let totalLtc = 0;
trades.forEach((trade) => {
const qort = parseFloat(trade.qortAmount);
const ltc = parseFloat(trade.foreignAmount);
totalQort += qort;
totalLtc += ltc;
});
// Avoid division by zero
if (totalQort === 0) return 0;
// Weighted average price
return parseFloat((totalLtc / totalQort).toFixed(8));
}
function getTwoWeeksAgoTimestamp() {
const now = new Date();
now.setDate(now.getDate() - 14); // Subtract 14 days
return now.getTime(); // Get timestamp in milliseconds
}
function formatWithCommasAndDecimals(number) {
return Number(number).toLocaleString();
}
export const QortPrice = () => {
const [ltcPerQort, setLtcPerQort] = useState(null)
const [supply, setSupply] = useState(null)
const [lastBlock, setLastBlock] = useState(null)
const [loading, setLoading] = useState(true)
const getPrice = useCallback(async () => {
try {
setLoading(true)
const response = await fetch(`${getBaseApiReact()}/crosschain/trades?foreignBlockchain=LITECOIN&minimumTimestamp=${getTwoWeeksAgoTimestamp()}&limit=20&reverse=true`);
const data = await response.json();
setLtcPerQort(getAverageLtcPerQort(data));
} catch (error) {
console.error(error);
} finally {
setLoading(false)
}
}, [])
const getLastBlock = useCallback(async () => {
try {
setLoading(true)
const response = await fetch(`${getBaseApiReact()}/blocks/last`);
const data = await response.json();
setLastBlock(data);
} catch (error) {
console.error(error);
} finally {
setLoading(false)
}
}, [])
const getSupplyInCirculation = useCallback(async () => {
try {
setLoading(true)
const response = await fetch(`${getBaseApiReact()}/stats/supply/circulating`);
const data = await response.text();
formatWithCommasAndDecimals(data)
setSupply(formatWithCommasAndDecimals(data));
} catch (error) {
console.error(error);
} finally {
setLoading(false)
}
}, [])
useEffect(() => {
getPrice();
getSupplyInCirculation()
getLastBlock()
const interval = setInterval(() => {
getPrice();
getSupplyInCirculation()
getLastBlock()
}, 900000);
return () => clearInterval(interval);
}, [getPrice]);
console.log('supply', supply)
return (
<Box
sx={{
display: "flex",
gap: "20px",
flexWrap: "wrap",
flexDirection: 'column',
width: "322px"
}}
>
<Tooltip
title={<span style={{ color: "white", fontSize: "14px", fontWeight: 700 }}>Based on the latest 20 trades</span>}
placement="bottom"
arrow
sx={{ fontSize: "24" }}
slotProps={{
tooltip: {
sx: {
color: "#ffffff",
backgroundColor: "#444444",
},
},
arrow: {
sx: {
color: "#444444",
},
},
}}
>
<Box sx={{
width: "322px",
display: "flex",
flexDirection: "row",
gap: '10px',
justifyContent: 'space-between'
}}>
<Typography sx={{
fontSize: "1rem",
fontWeight: 'bold'
}}>Price</Typography>
{!ltcPerQort ? (
<BarSpinner width="16px" color="white" />
): (
<Typography sx={{
fontSize: "1rem",
}}>{ltcPerQort} LTC/QORT</Typography>
)}
</Box>
</Tooltip>
<Box sx={{
width: "322px",
display: "flex",
flexDirection: "row",
gap: '10px',
justifyContent: 'space-between'
}}>
<Typography sx={{
fontSize: "1rem",
fontWeight: 'bold'
}}>Supply</Typography>
{!supply ? (
<BarSpinner width="16px" color="white" />
): (
<Typography sx={{
fontSize: "1rem",
}}>{supply} QORT</Typography>
)}
</Box>
<Box sx={{
width: "322px",
display: "flex",
flexDirection: "row",
gap: '10px',
justifyContent: 'space-between'
}}>
<Typography sx={{
fontSize: "1rem",
fontWeight: 'bold'
}}>Last height</Typography>
{!lastBlock?.height ? (
<BarSpinner width="16px" color="white" />
): (
<Typography sx={{
fontSize: "1rem",
}}>{lastBlock?.height}</Typography>
)}
</Box>
</Box>
)
}