mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-04-24 03:47:53 +00:00
reworked homepage
This commit is contained in:
parent
519a0bb652
commit
ae5ca83963
BIN
src/assets/Icons/q-trade-logo.webp
Normal file
BIN
src/assets/Icons/q-trade-logo.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
10
src/common/Spinners/BarSpinner/BarSpinner.tsx
Normal file
10
src/common/Spinners/BarSpinner/BarSpinner.tsx
Normal 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>
|
||||
)
|
||||
}
|
19
src/common/Spinners/BarSpinner/barSpinner.css
Normal file
19
src/common/Spinners/BarSpinner/barSpinner.css
Normal 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% }
|
||||
}
|
101
src/components/Explore/Explore.tsx
Normal file
101
src/components/Explore/Explore.tsx
Normal 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>
|
||||
);
|
||||
};
|
@ -1299,11 +1299,11 @@ export const Group = ({
|
||||
if (isLoadingOpenSectionFromNotification.current) return;
|
||||
|
||||
const groupId = e.detail?.from;
|
||||
|
||||
const findGroup = groups?.find((group) => +group?.groupId === +groupId);
|
||||
if (findGroup?.groupId === selectedGroup?.groupId) {
|
||||
isLoadingOpenSectionFromNotification.current = false;
|
||||
|
||||
setChatMode("groups");
|
||||
setDesktopViewMode('chat')
|
||||
return;
|
||||
}
|
||||
if (findGroup) {
|
||||
|
@ -10,16 +10,20 @@ import CommentIcon from "@mui/icons-material/Comment";
|
||||
import InfoIcon from "@mui/icons-material/Info";
|
||||
import GroupAddIcon from "@mui/icons-material/GroupAdd";
|
||||
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 { getGroupNames } from "./UserListOfInvites";
|
||||
import { CustomLoader } from "../../common/CustomLoader";
|
||||
import { getBaseApiReact, isMobile } from "../../App";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
||||
|
||||
export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
|
||||
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState(
|
||||
[]
|
||||
);
|
||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
|
||||
const getJoinRequests = async () => {
|
||||
@ -53,121 +57,129 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
<ButtonBase
|
||||
sx={{
|
||||
width: "322px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexDirection: "row",
|
||||
padding: "0px 20px",
|
||||
gap: '10px',
|
||||
justifyContent: 'flex-start'
|
||||
}}
|
||||
onClick={()=> setIsExpanded((prev)=> !prev)}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontWeight: 600,
|
||||
fontSize: "1rem",
|
||||
}}
|
||||
>
|
||||
Group Invites:
|
||||
Group Invites {groupsWithJoinRequests?.length > 0 && ` (${groupsWithJoinRequests?.length})`}
|
||||
</Typography>
|
||||
<Spacer height="10px" />
|
||||
</Box>
|
||||
{isExpanded ? <ExpandLessIcon sx={{
|
||||
marginLeft: 'auto'
|
||||
}} /> : (
|
||||
<ExpandMoreIcon sx={{
|
||||
marginLeft: 'auto'
|
||||
}}/>
|
||||
)}
|
||||
</ButtonBase>
|
||||
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
|
||||
<Box
|
||||
sx={{
|
||||
width: "322px",
|
||||
height: isMobile ? "165px" : "250px",
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
width: "322px",
|
||||
height: isMobile ? "165px" : "250px",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
bgcolor: "background.paper",
|
||||
padding: "20px",
|
||||
borderRadius: "19px",
|
||||
}}
|
||||
>
|
||||
{loading && groupsWithJoinRequests.length === 0 && (
|
||||
<Box
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
bgcolor: "background.paper",
|
||||
padding: "20px",
|
||||
borderRadius: "19px",
|
||||
}}
|
||||
>
|
||||
{loading && groupsWithJoinRequests.length === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<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={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
maxWidth: 360,
|
||||
bgcolor: "background.paper",
|
||||
maxHeight: "300px",
|
||||
overflow: "auto",
|
||||
}}
|
||||
className="scrollable-container"
|
||||
>
|
||||
<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={{
|
||||
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
|
||||
{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={{
|
||||
color: "white",
|
||||
fontSize: "18px",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
<ListItemButton disableRipple role={undefined} dense>
|
||||
<ListItemText
|
||||
sx={{
|
||||
color: "white",
|
||||
fontSize: "18px",
|
||||
"& .MuiTypography-root": {
|
||||
fontSize: "13px",
|
||||
fontWeight: 400,
|
||||
},
|
||||
}}
|
||||
primary={`${group?.groupName} has invited you`}
|
||||
/>
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
<ListItemButton disableRipple role={undefined} dense>
|
||||
<ListItemText
|
||||
sx={{
|
||||
"& .MuiTypography-root": {
|
||||
fontSize: "13px",
|
||||
fontWeight: 400,
|
||||
},
|
||||
}}
|
||||
primary={`${group?.groupName} has invited you`}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</Box>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -11,16 +11,20 @@ import InfoIcon from "@mui/icons-material/Info";
|
||||
import { RequestQueueWithPromise } from "../../utils/queue/queue";
|
||||
import GroupAddIcon from '@mui/icons-material/GroupAdd';
|
||||
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 { CustomLoader } from "../../common/CustomLoader";
|
||||
import { getBaseApi } from "../../background";
|
||||
import { MyContext, getBaseApiReact, isMobile } from "../../App";
|
||||
import { myGroupsWhereIAmAdminAtom } from "../../atoms/global";
|
||||
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 GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, getTimestampEnterChat, setSelectedGroup, setGroupSection, setMobileViewMode, setDesktopViewMode }) => {
|
||||
const [isExpanded, setIsExpanded] = React.useState(false)
|
||||
|
||||
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState([])
|
||||
const [loading, setLoading] = React.useState(true)
|
||||
const {txList, setTxList} = React.useContext(MyContext)
|
||||
@ -109,26 +113,33 @@ export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, get
|
||||
flexDirection: "column",
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
<Box
|
||||
<ButtonBase
|
||||
sx={{
|
||||
width: "322px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexDirection: "row",
|
||||
padding: '0px 20px',
|
||||
|
||||
gap: '10px',
|
||||
justifyContent: 'flex-start'
|
||||
}}
|
||||
onClick={()=> setIsExpanded((prev)=> !prev)}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontWeight: 600,
|
||||
fontSize: "1rem",
|
||||
}}
|
||||
>
|
||||
Join Requests:
|
||||
Join Requests {filteredJoinRequests?.filter((group)=> group?.data?.length > 0)?.length > 0 && ` (${filteredJoinRequests?.filter((group)=> group?.data?.length > 0)?.length})`}
|
||||
</Typography>
|
||||
<Spacer height="10px" />
|
||||
</Box>
|
||||
|
||||
{isExpanded ? <ExpandLessIcon sx={{
|
||||
marginLeft: 'auto'
|
||||
}} /> : (
|
||||
<ExpandMoreIcon sx={{
|
||||
marginLeft: 'auto'
|
||||
}}/>
|
||||
)}
|
||||
</ButtonBase>
|
||||
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
|
||||
<Box
|
||||
sx={{
|
||||
width: "322px",
|
||||
@ -227,6 +238,7 @@ export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, get
|
||||
|
||||
</List>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Box, Button, Typography } from "@mui/material";
|
||||
import { Box, Button, Divider, Typography } from "@mui/material";
|
||||
import React from "react";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { ListOfThreadPostsWatched } from "./ListOfThreadPostsWatched";
|
||||
@ -7,7 +7,10 @@ import { GroupJoinRequests } from "./GroupJoinRequests";
|
||||
import { GroupInvites } from "./GroupInvites";
|
||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||
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 = ({
|
||||
refreshHomeDataFunc,
|
||||
myAddress,
|
||||
@ -22,12 +25,12 @@ export const HomeDesktop = ({
|
||||
setOpenAddGroup,
|
||||
setMobileViewMode,
|
||||
setDesktopViewMode,
|
||||
desktopViewMode
|
||||
desktopViewMode,
|
||||
}) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: desktopViewMode === 'home' ? "flex" : "none",
|
||||
display: desktopViewMode === "home" ? "flex" : "none",
|
||||
width: "100%",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
@ -36,105 +39,169 @@ export const HomeDesktop = ({
|
||||
}}
|
||||
>
|
||||
<Spacer height="20px" />
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
alignItems: "flex-start",
|
||||
maxWidth: '1036px'
|
||||
}}>
|
||||
<Typography
|
||||
<Box
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 1)",
|
||||
fontWeight: 400,
|
||||
fontSize: userInfo?.name?.length > 15 ? "16px" : "20px",
|
||||
padding: '10px'
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
alignItems: "flex-start",
|
||||
maxWidth: "1036px",
|
||||
}}
|
||||
>
|
||||
Welcome
|
||||
{userInfo?.name ? (
|
||||
<span
|
||||
style={{
|
||||
fontStyle: "italic",
|
||||
}}
|
||||
>{`, ${userInfo?.name}`}</span>
|
||||
) : null}
|
||||
</Typography>
|
||||
<Spacer height="30px" />
|
||||
{!isLoadingGroups && (
|
||||
<Box
|
||||
<Typography
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "15px",
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "center",
|
||||
color: "rgba(255, 255, 255, 1)",
|
||||
fontWeight: 400,
|
||||
fontSize: userInfo?.name?.length > 15 ? "16px" : "20px",
|
||||
padding: "10px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{
|
||||
width: '330px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<ThingsToDoInitial
|
||||
balance={balance}
|
||||
myAddress={myAddress}
|
||||
name={userInfo?.name}
|
||||
userInfo={userInfo}
|
||||
hasGroups={groups?.length !== 0}
|
||||
/>
|
||||
</Box>
|
||||
{desktopViewMode === 'home' && (
|
||||
<>
|
||||
<Box sx={{
|
||||
Welcome
|
||||
{userInfo?.name ? (
|
||||
<span
|
||||
style={{
|
||||
fontStyle: "italic",
|
||||
}}
|
||||
>{`, ${userInfo?.name}`}</span>
|
||||
) : null}
|
||||
</Typography>
|
||||
<Spacer height="30px" />
|
||||
{!isLoadingGroups && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
flexWrap: "wrap",
|
||||
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',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<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 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}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isLoadingGroups && (
|
||||
<>
|
||||
<Spacer height="60px" />
|
||||
<Divider
|
||||
color="secondary"
|
||||
sx={{
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<ExploreIcon
|
||||
sx={{
|
||||
color: "white",
|
||||
}}
|
||||
/>{" "}
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "1rem",
|
||||
}}
|
||||
>
|
||||
Explore
|
||||
</Typography>{" "}
|
||||
</Box>
|
||||
<Box sx={{
|
||||
width: '330px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<GroupInvites
|
||||
setOpenAddGroup={setOpenAddGroup}
|
||||
myAddress={myAddress}
|
||||
groups={groups}
|
||||
setMobileViewMode={setMobileViewMode}
|
||||
/>
|
||||
</Divider>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
flexWrap: "wrap",
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<ListOfGroupPromotions />
|
||||
<Explore setDesktopViewMode={setDesktopViewMode} />
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
</Box>
|
||||
)}
|
||||
{!isLoadingGroups && (
|
||||
<ListOfGroupPromotions />
|
||||
)}
|
||||
<NewUsersCTA balance={balance} />
|
||||
</>
|
||||
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Spacer height="26px" />
|
||||
|
||||
{/* <Box
|
||||
@ -155,7 +222,7 @@ export const HomeDesktop = ({
|
||||
Refresh home data
|
||||
</Button>
|
||||
</Box> */}
|
||||
|
||||
|
||||
<Spacer height="180px" />
|
||||
</Box>
|
||||
);
|
||||
|
@ -9,6 +9,8 @@ import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
ButtonBase,
|
||||
Collapse,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
@ -28,8 +30,8 @@ import {
|
||||
import { getNameInfo } from "./Group";
|
||||
import { getBaseApi, getFee } from "../../background";
|
||||
import { LoadingButton } from "@mui/lab";
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred';
|
||||
import LockIcon from "@mui/icons-material/Lock";
|
||||
import NoEncryptionGmailerrorredIcon from "@mui/icons-material/NoEncryptionGmailerrorred";
|
||||
import {
|
||||
MyContext,
|
||||
getArbitraryEndpointReact,
|
||||
@ -40,7 +42,11 @@ import { Spacer } from "../../common/Spacer";
|
||||
import { CustomLoader } from "../../common/CustomLoader";
|
||||
import { RequestQueueWithPromise } from "../../utils/queue/queue";
|
||||
import { useRecoilState } from "recoil";
|
||||
import { myGroupsWhereIAmAdminAtom, promotionTimeIntervalAtom, promotionsAtom } from "../../atoms/global";
|
||||
import {
|
||||
myGroupsWhereIAmAdminAtom,
|
||||
promotionTimeIntervalAtom,
|
||||
promotionsAtom,
|
||||
} from "../../atoms/global";
|
||||
import { Label } from "./AddGroup";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
|
||||
@ -48,7 +54,8 @@ import { getGroupNames } from "./UserListOfInvites";
|
||||
import { WrapperUserAction } from "../WrapperUserAction";
|
||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||
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 function utf8ToBase64(inputString: string): string {
|
||||
@ -65,8 +72,6 @@ export function utf8ToBase64(inputString: string): string {
|
||||
|
||||
const uid = new ShortUniqueId({ length: 8 });
|
||||
|
||||
|
||||
|
||||
export function getGroupId(str) {
|
||||
const match = str.match(/group-(\d+)-/);
|
||||
return match ? match[1] : null;
|
||||
@ -82,12 +87,12 @@ export const ListOfGroupPromotions = () => {
|
||||
const [myGroupsWhereIAmAdmin, setMyGroupsWhereIAmAdmin] = useRecoilState(
|
||||
myGroupsWhereIAmAdminAtom
|
||||
);
|
||||
const [promotions, setPromotions] = useRecoilState(
|
||||
promotionsAtom
|
||||
);
|
||||
const [promotions, setPromotions] = useRecoilState(promotionsAtom);
|
||||
const [promotionTimeInterval, setPromotionTimeInterval] = useRecoilState(
|
||||
promotionTimeIntervalAtom
|
||||
);
|
||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||
|
||||
const [openSnack, setOpenSnack] = useState(false);
|
||||
const [infoSnack, setInfoSnack] = useState(null);
|
||||
const [fee, setFee] = useState(null);
|
||||
@ -96,16 +101,16 @@ export const ListOfGroupPromotions = () => {
|
||||
const { show, setTxList } = useContext(MyContext);
|
||||
|
||||
const listRef = useRef();
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
count: promotions.length,
|
||||
getItemKey: React.useCallback(
|
||||
(index) => promotions[index]?.identifier,
|
||||
[promotions]
|
||||
),
|
||||
getScrollElement: () => listRef.current,
|
||||
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
|
||||
});
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
count: promotions.length,
|
||||
getItemKey: React.useCallback(
|
||||
(index) => promotions[index]?.identifier,
|
||||
[promotions]
|
||||
),
|
||||
getScrollElement: () => listRef.current,
|
||||
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
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
@ -117,7 +122,7 @@ export const ListOfGroupPromotions = () => {
|
||||
}, []);
|
||||
const getPromotions = useCallback(async () => {
|
||||
try {
|
||||
setPromotionTimeInterval(Date.now())
|
||||
setPromotionTimeInterval(Date.now());
|
||||
const identifier = `group-promotions-ui24-`;
|
||||
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=100&includemetadata=false&reverse=true&prefix=true`;
|
||||
const response = await fetch(url, {
|
||||
@ -168,7 +173,9 @@ export const ListOfGroupPromotions = () => {
|
||||
});
|
||||
|
||||
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);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@ -177,22 +184,23 @@ export const ListOfGroupPromotions = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const now = Date.now();
|
||||
|
||||
|
||||
const timeSinceLastFetch = now - promotionTimeInterval;
|
||||
const initialDelay = timeSinceLastFetch >= THIRTY_MINUTES
|
||||
? 0
|
||||
: THIRTY_MINUTES - timeSinceLastFetch;
|
||||
const initialDelay =
|
||||
timeSinceLastFetch >= THIRTY_MINUTES
|
||||
? 0
|
||||
: THIRTY_MINUTES - timeSinceLastFetch;
|
||||
const initialTimeout = setTimeout(() => {
|
||||
getPromotions();
|
||||
|
||||
|
||||
// Start a 30-minute interval
|
||||
const interval = setInterval(() => {
|
||||
getPromotions();
|
||||
}, THIRTY_MINUTES);
|
||||
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, initialDelay);
|
||||
|
||||
|
||||
return () => clearTimeout(initialTimeout);
|
||||
}, [getPromotions, promotionTimeInterval]);
|
||||
|
||||
@ -328,100 +336,143 @@ export const ListOfGroupPromotions = () => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
marginTop: "20px",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
marginTop: "25px",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "320px" : "750px",
|
||||
maxWidth: "90%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: "0px 20px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
gap: '20px',
|
||||
width: '100%',
|
||||
justifyContent: 'space-between'
|
||||
}}>
|
||||
<ButtonBase
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
padding: `0px ${isExpanded ? "24px" : "20px"}`,
|
||||
gap: "10px",
|
||||
justifyContent: "flex-start",
|
||||
alignSelf: isExpanded && "flex-start",
|
||||
}}
|
||||
onClick={() => setIsExpanded((prev) => !prev)}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontWeight: 600,
|
||||
fontSize: "1rem",
|
||||
}}
|
||||
>
|
||||
Group Promotions
|
||||
Group promotions {promotions.length > 0 && ` (${promotions.length})`}
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => setIsShowModal(true)}
|
||||
sx={{
|
||||
fontSize: "12px",
|
||||
}}
|
||||
>
|
||||
Add Promotion
|
||||
</Button>
|
||||
</Box>
|
||||
<Spacer height="10px" />
|
||||
{isExpanded ? (
|
||||
<ExpandLessIcon
|
||||
sx={{
|
||||
marginLeft: "auto",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ExpandMoreIcon
|
||||
sx={{
|
||||
marginLeft: "auto",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ButtonBase>
|
||||
<Box
|
||||
style={{
|
||||
width: "330px",
|
||||
}}
|
||||
/>
|
||||
</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 && (
|
||||
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
width: isMobile ? "320px" : "750px",
|
||||
maxWidth: "90%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
flexDirection: "column",
|
||||
padding: "0px 20px",
|
||||
}}
|
||||
>
|
||||
<CustomLoader />
|
||||
</Box>
|
||||
)}
|
||||
{!loading && promotions.length === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "11px",
|
||||
fontWeight: 400,
|
||||
color: "rgba(255, 255, 255, 0.2)",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
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
|
||||
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
|
||||
style={{
|
||||
height: "600px",
|
||||
@ -460,7 +511,6 @@ export const ListOfGroupPromotions = () => {
|
||||
const index = virtualRow.index;
|
||||
const promotion = promotions[index];
|
||||
return (
|
||||
|
||||
<div
|
||||
data-index={virtualRow.index} //needed for dynamic row height measurement
|
||||
ref={rowVirtualizer.measureElement} //measure dynamic row height
|
||||
@ -479,236 +529,251 @@ export const ListOfGroupPromotions = () => {
|
||||
gap: "5px",
|
||||
}}
|
||||
>
|
||||
<ErrorBoundary
|
||||
fallback={
|
||||
<Typography>
|
||||
Error loading content: Invalid Data
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
padding: "0px 20px",
|
||||
}}
|
||||
>
|
||||
<Popover
|
||||
open={openPopoverIndex === promotion?.groupId}
|
||||
anchorEl={popoverAnchor}
|
||||
onClose={(event, reason) => {
|
||||
if (reason === "backdropClick") {
|
||||
// Prevent closing on backdrop click
|
||||
return;
|
||||
}
|
||||
handlePopoverClose(); // Close only on other events like Esc key press
|
||||
}}
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
}}
|
||||
style={{ marginTop: "8px" }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "325px",
|
||||
height: "auto",
|
||||
maxHeight: "400px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
padding: "10px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Group name: {` ${promotion?.groupName}`}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Number of members: {` ${promotion?.memberCount}`}
|
||||
</Typography>
|
||||
{promotion?.description && (
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{promotion?.description}
|
||||
</Typography>
|
||||
)}
|
||||
{promotion?.isOpen === false && (
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
*This is a closed/private group, so you will need to wait
|
||||
until an admin accepts your request
|
||||
</Typography>
|
||||
)}
|
||||
<Spacer height="5px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<LoadingButton
|
||||
loading={isLoadingJoinGroup}
|
||||
loadingPosition="start"
|
||||
variant="contained"
|
||||
onClick={handlePopoverClose}
|
||||
>
|
||||
Close
|
||||
</LoadingButton>
|
||||
<LoadingButton
|
||||
loading={isLoadingJoinGroup}
|
||||
loadingPosition="start"
|
||||
variant="contained"
|
||||
onClick={() =>
|
||||
handleJoinGroup(promotion, promotion?.isOpen)
|
||||
}
|
||||
>
|
||||
Join
|
||||
</LoadingButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</Popover>
|
||||
<ErrorBoundary
|
||||
fallback={
|
||||
<Typography>
|
||||
Error loading content: Invalid Data
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
padding: "0px 20px",
|
||||
}}
|
||||
>
|
||||
<Popover
|
||||
open={openPopoverIndex === promotion?.groupId}
|
||||
anchorEl={popoverAnchor}
|
||||
onClose={(event, reason) => {
|
||||
if (reason === "backdropClick") {
|
||||
// Prevent closing on backdrop click
|
||||
return;
|
||||
}
|
||||
handlePopoverClose(); // Close only on other events like Esc key press
|
||||
}}
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
}}
|
||||
style={{ marginTop: "8px" }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "325px",
|
||||
height: "auto",
|
||||
maxHeight: "400px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
padding: "10px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Group name: {` ${promotion?.groupName}`}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Number of members:{" "}
|
||||
{` ${promotion?.memberCount}`}
|
||||
</Typography>
|
||||
{promotion?.description && (
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{promotion?.description}
|
||||
</Typography>
|
||||
)}
|
||||
{promotion?.isOpen === false && (
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
*This is a closed/private group, so you
|
||||
will need to wait until an admin accepts
|
||||
your request
|
||||
</Typography>
|
||||
)}
|
||||
<Spacer height="5px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<LoadingButton
|
||||
loading={isLoadingJoinGroup}
|
||||
loadingPosition="start"
|
||||
variant="contained"
|
||||
onClick={handlePopoverClose}
|
||||
>
|
||||
Close
|
||||
</LoadingButton>
|
||||
<LoadingButton
|
||||
loading={isLoadingJoinGroup}
|
||||
loadingPosition="start"
|
||||
variant="contained"
|
||||
onClick={() =>
|
||||
handleJoinGroup(
|
||||
promotion,
|
||||
promotion?.isOpen
|
||||
)
|
||||
}
|
||||
>
|
||||
Join
|
||||
</LoadingButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</Popover>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{
|
||||
backgroundColor: "#27282c",
|
||||
color: "white",
|
||||
}}
|
||||
alt={promotion?.name}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||
promotion?.name
|
||||
}/qortal_avatar?async=true`}
|
||||
>
|
||||
{promotion?.name?.charAt(0)}
|
||||
</Avatar>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWight: 600,
|
||||
fontFamily: "Inter",
|
||||
color: "cadetBlue",
|
||||
}}
|
||||
>
|
||||
{promotion?.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWight: 600,
|
||||
fontFamily: "Inter",
|
||||
color: "cadetBlue",
|
||||
}}
|
||||
>
|
||||
{promotion?.groupName}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Spacer height="20px" />
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
gap: '20px',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
{promotion?.isOpen === false && (
|
||||
<LockIcon sx={{
|
||||
color: 'var(--green)'
|
||||
}} />
|
||||
)}
|
||||
{promotion?.isOpen === true && (
|
||||
<NoEncryptionGmailerrorredIcon sx={{
|
||||
color: 'var(--danger)'
|
||||
}} />
|
||||
)}
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "15px",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{promotion?.isOpen ? 'Public group' : 'Private group' }
|
||||
</Typography>
|
||||
</Box>
|
||||
<Spacer height="20px" />
|
||||
<Typography
|
||||
sx={{
|
||||
fontWight: 600,
|
||||
fontFamily: "Inter",
|
||||
color: "cadetBlue",
|
||||
}}
|
||||
>
|
||||
{promotion?.data}
|
||||
</Typography>
|
||||
<Spacer height="20px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
// variant="contained"
|
||||
onClick={(event) => handlePopoverOpen(event, promotion?.groupId)}
|
||||
sx={{
|
||||
fontSize: "12px",
|
||||
color: 'white'
|
||||
}}
|
||||
>
|
||||
Join Group: {` ${promotion?.groupName}`}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
<Spacer height="50px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{
|
||||
backgroundColor: "#27282c",
|
||||
color: "white",
|
||||
}}
|
||||
alt={promotion?.name}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||
promotion?.name
|
||||
}/qortal_avatar?async=true`}
|
||||
>
|
||||
{promotion?.name?.charAt(0)}
|
||||
</Avatar>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWight: 600,
|
||||
fontFamily: "Inter",
|
||||
color: "cadetBlue",
|
||||
}}
|
||||
>
|
||||
{promotion?.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWight: 600,
|
||||
fontFamily: "Inter",
|
||||
color: "cadetBlue",
|
||||
}}
|
||||
>
|
||||
{promotion?.groupName}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Spacer height="20px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{promotion?.isOpen === false && (
|
||||
<LockIcon
|
||||
sx={{
|
||||
color: "var(--green)",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{promotion?.isOpen === true && (
|
||||
<NoEncryptionGmailerrorredIcon
|
||||
sx={{
|
||||
color: "var(--danger)",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "15px",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{promotion?.isOpen
|
||||
? "Public group"
|
||||
: "Private group"}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Spacer height="20px" />
|
||||
<Typography
|
||||
sx={{
|
||||
fontWight: 600,
|
||||
fontFamily: "Inter",
|
||||
color: "cadetBlue",
|
||||
}}
|
||||
>
|
||||
{promotion?.data}
|
||||
</Typography>
|
||||
<Spacer height="20px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
// variant="contained"
|
||||
onClick={(event) =>
|
||||
handlePopoverOpen(event, promotion?.groupId)
|
||||
}
|
||||
sx={{
|
||||
fontSize: "12px",
|
||||
color: "white",
|
||||
}}
|
||||
>
|
||||
Join Group: {` ${promotion?.groupName}`}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
<Spacer height="50px" />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
</Collapse>
|
||||
<Spacer height="20px" />
|
||||
|
||||
{isShowModal && (
|
||||
|
@ -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 ListItem from "@mui/material/ListItem";
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import moment from 'moment'
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { Box, ButtonBase, Collapse, Typography } from "@mui/material";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { getBaseApiReact, isMobile } from "../../App";
|
||||
import { MessagingIcon } from '../../assets/Icons/MessagingIcon';
|
||||
@ -15,6 +15,10 @@ import { executeEvent } from '../../utils/events';
|
||||
import { CustomLoader } from '../../common/CustomLoader';
|
||||
import { useRecoilState } from 'recoil';
|
||||
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) => {
|
||||
// Current time in milliseconds
|
||||
const now = Date.now();
|
||||
@ -41,6 +45,7 @@ export function formatEmailDate(timestamp: number) {
|
||||
}
|
||||
}
|
||||
export const QMailMessages = ({userName, userAddress}) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
const [mails, setMails] = useRecoilState(mailsAtom)
|
||||
const [lastEnteredTimestamp, setLastEnteredTimestamp] = useRecoilState(qMailLastEnteredTimestampAtom)
|
||||
const [loading, setLoading] = useState(true)
|
||||
@ -99,7 +104,16 @@ export const QMailMessages = ({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 (
|
||||
<Box
|
||||
@ -111,25 +125,37 @@ export const QMailMessages = ({userName, userAddress}) => {
|
||||
}}
|
||||
>
|
||||
|
||||
<Box
|
||||
<ButtonBase
|
||||
sx={{
|
||||
width: "322px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexDirection: "row",
|
||||
gap: '10px',
|
||||
padding: "0px 20px",
|
||||
justifyContent: 'flex-start'
|
||||
}}
|
||||
onClick={()=> setIsExpanded((prev)=> !prev)}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontWeight: 600,
|
||||
fontSize: "1rem",
|
||||
}}
|
||||
>
|
||||
Latest Q-Mails
|
||||
</Typography>
|
||||
<Spacer height="10px" />
|
||||
</Box>
|
||||
|
||||
<MarkEmailUnreadIcon sx={{
|
||||
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
|
||||
className="scrollable-container"
|
||||
sx={{
|
||||
@ -248,6 +274,7 @@ export const QMailMessages = ({userName, userAddress}) => {
|
||||
|
||||
|
||||
</Box>
|
||||
</Collapse>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ if(hasDoneNameAndBalanceAndIsLoaded){
|
||||
<QMailMessages userAddress={userInfo?.address} userName={userInfo?.name} />
|
||||
);
|
||||
}
|
||||
if(!isLoaded) return null
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -84,7 +85,7 @@ if(hasDoneNameAndBalanceAndIsLoaded){
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontSize: "1rem",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
@ -96,7 +97,6 @@ if(hasDoneNameAndBalanceAndIsLoaded){
|
||||
<Box
|
||||
sx={{
|
||||
width: "322px",
|
||||
height: isMobile ? "165px" : "250px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
bgcolor: "background.paper",
|
||||
|
91
src/components/Home/NewUsersCTA.tsx
Normal file
91
src/components/Home/NewUsersCTA.tsx
Normal 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>
|
||||
);
|
||||
};
|
209
src/components/Home/QortPrice.tsx
Normal file
209
src/components/Home/QortPrice.tsx
Normal 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>
|
||||
)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user