added badges in chat
@ -127,6 +127,7 @@ import { Wallets } from "./Wallets";
|
|||||||
import { useHandleTutorials } from "./components/Tutorials/useHandleTutorials";
|
import { useHandleTutorials } from "./components/Tutorials/useHandleTutorials";
|
||||||
import { Tutorials } from "./components/Tutorials/Tutorials";
|
import { Tutorials } from "./components/Tutorials/Tutorials";
|
||||||
import BoundedNumericTextField from "./common/BoundedNumericTextField";
|
import BoundedNumericTextField from "./common/BoundedNumericTextField";
|
||||||
|
import { useHandleUserInfo } from "./components/Group/useHandleUserInfo";
|
||||||
|
|
||||||
|
|
||||||
type extStates =
|
type extStates =
|
||||||
@ -455,6 +456,7 @@ function App() {
|
|||||||
useRetrieveDataLocalStorage();
|
useRetrieveDataLocalStorage();
|
||||||
useQortalGetSaveSettings(userInfo?.name, extState === "authenticated");
|
useQortalGetSaveSettings(userInfo?.name, extState === "authenticated");
|
||||||
const [fullScreen, setFullScreen] = useRecoilState(fullScreenAtom);
|
const [fullScreen, setFullScreen] = useRecoilState(fullScreenAtom);
|
||||||
|
const {getIndividualUserInfo} = useHandleUserInfo()
|
||||||
|
|
||||||
const { toggleFullScreen } = useAppFullScreen(setFullScreen);
|
const { toggleFullScreen } = useAppFullScreen(setFullScreen);
|
||||||
const generatorRef = useRef(null)
|
const generatorRef = useRef(null)
|
||||||
@ -1738,7 +1740,8 @@ console.log('openTutorialModal3', openTutorialModal)
|
|||||||
infoSnackCustom: infoSnack,
|
infoSnackCustom: infoSnack,
|
||||||
setInfoSnackCustom: setInfoSnack,
|
setInfoSnackCustom: setInfoSnack,
|
||||||
userInfo: userInfo,
|
userInfo: userInfo,
|
||||||
downloadResource
|
downloadResource,
|
||||||
|
getIndividualUserInfo
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
|
BIN
src/assets/badges/level-0.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
src/assets/badges/level-1.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
src/assets/badges/level-10.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
src/assets/badges/level-2.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
src/assets/badges/level-3.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
src/assets/badges/level-4.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
src/assets/badges/level-5.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
src/assets/badges/level-6.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
src/assets/badges/level-7.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
src/assets/badges/level-8.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
src/assets/badges/level-9.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
@ -145,3 +145,16 @@ export const isUsingImportExportSettingsAtom = atom({
|
|||||||
key: 'isUsingImportExportSettingsAtom',
|
key: 'isUsingImportExportSettingsAtom',
|
||||||
default: null,
|
default: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const addressInfoControllerAtom = atom({
|
||||||
|
key: 'addressInfoControllerAtom',
|
||||||
|
default: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const addressInfoKeySelector = selectorFamily({
|
||||||
|
key: 'addressInfoKeySelector',
|
||||||
|
get: (key) => ({ get }) => {
|
||||||
|
const userInfo = get(addressInfoControllerAtom);
|
||||||
|
return userInfo[key] || null; // Return the value for the key or null if not found
|
||||||
|
},
|
||||||
|
});
|
@ -1,11 +1,11 @@
|
|||||||
import { Message } from "@chatscope/chat-ui-kit-react";
|
import { Message } from "@chatscope/chat-ui-kit-react";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useContext, useEffect, useState } from "react";
|
||||||
import { useInView } from "react-intersection-observer";
|
import { useInView } from "react-intersection-observer";
|
||||||
import { MessageDisplay } from "./MessageDisplay";
|
import { MessageDisplay } from "./MessageDisplay";
|
||||||
import { Avatar, Box, Button, ButtonBase, List, ListItem, ListItemText, Popover, Typography } from "@mui/material";
|
import { Avatar, Box, Button, ButtonBase, ClickAwayListener, List, ListItem, ListItemText, Popover, Tooltip, Typography } from "@mui/material";
|
||||||
import { formatTimestamp } from "../../utils/time";
|
import { formatTimestamp } from "../../utils/time";
|
||||||
import { getBaseApi } from "../../background";
|
import { getBaseApi } from "../../background";
|
||||||
import { getBaseApiReact } from "../../App";
|
import { MyContext, getBaseApiReact } from "../../App";
|
||||||
import { generateHTML } from "@tiptap/react";
|
import { generateHTML } from "@tiptap/react";
|
||||||
import Highlight from "@tiptap/extension-highlight";
|
import Highlight from "@tiptap/extension-highlight";
|
||||||
import StarterKit from "@tiptap/starter-kit";
|
import StarterKit from "@tiptap/starter-kit";
|
||||||
@ -19,6 +19,38 @@ import KeyOffIcon from '@mui/icons-material/KeyOff';
|
|||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
import Mention from "@tiptap/extension-mention";
|
import Mention from "@tiptap/extension-mention";
|
||||||
import TextStyle from '@tiptap/extension-text-style';
|
import TextStyle from '@tiptap/extension-text-style';
|
||||||
|
import { useRecoilValue } from "recoil";
|
||||||
|
import { addressInfoKeySelector } from "../../atoms/global";
|
||||||
|
|
||||||
|
import level0Img from "../../assets/badges/level-0.png"
|
||||||
|
import level1Img from "../../assets/badges/level-1.png"
|
||||||
|
import level2Img from "../../assets/badges/level-2.png"
|
||||||
|
import level3Img from "../../assets/badges/level-3.png"
|
||||||
|
import level4Img from "../../assets/badges/level-4.png"
|
||||||
|
import level5Img from "../../assets/badges/level-5.png"
|
||||||
|
import level6Img from "../../assets/badges/level-6.png"
|
||||||
|
import level7Img from "../../assets/badges/level-7.png"
|
||||||
|
import level8Img from "../../assets/badges/level-8.png"
|
||||||
|
import level9Img from "../../assets/badges/level-9.png"
|
||||||
|
import level10Img from "../../assets/badges/level-10.png"
|
||||||
|
|
||||||
|
const getBadgeImg = (level)=> {
|
||||||
|
switch(level?.toString()){
|
||||||
|
|
||||||
|
case '0': return level0Img
|
||||||
|
case '1': return level1Img
|
||||||
|
case '2': return level2Img
|
||||||
|
case '3': return level3Img
|
||||||
|
case '4': return level4Img
|
||||||
|
case '5': return level5Img
|
||||||
|
case '6': return level6Img
|
||||||
|
case '7': return level7Img
|
||||||
|
case '8': return level8Img
|
||||||
|
case '9': return level9Img
|
||||||
|
case '10': return level10Img
|
||||||
|
default: return level0Img
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const MessageItem = ({
|
export const MessageItem = ({
|
||||||
message,
|
message,
|
||||||
@ -39,6 +71,18 @@ export const MessageItem = ({
|
|||||||
isPrivate,
|
isPrivate,
|
||||||
setMobileViewModeKeepOpen
|
setMobileViewModeKeepOpen
|
||||||
}) => {
|
}) => {
|
||||||
|
const {getIndividualUserInfo} = useContext(MyContext)
|
||||||
|
const userInfo = useRecoilValue(addressInfoKeySelector(message?.sender));
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleTooltipClose = () => {
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTooltipOpen = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
const [anchorEl, setAnchorEl] = useState(null);
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
const [selectedReaction, setSelectedReaction] = useState(null);
|
const [selectedReaction, setSelectedReaction] = useState(null);
|
||||||
const { ref, inView } = useInView({
|
const { ref, inView } = useInView({
|
||||||
@ -52,6 +96,11 @@ export const MessageItem = ({
|
|||||||
}
|
}
|
||||||
}, [inView, message.id, isLast]);
|
}, [inView, message.id, isLast]);
|
||||||
|
|
||||||
|
useEffect(()=> {
|
||||||
|
if(message?.sender){
|
||||||
|
getIndividualUserInfo(message?.sender)
|
||||||
|
}
|
||||||
|
}, [message?.sender])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -80,11 +129,18 @@ export const MessageItem = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '20px',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}>
|
||||||
<WrapperUserAction
|
<WrapperUserAction
|
||||||
disabled={myAddress === message?.sender}
|
disabled={myAddress === message?.sender}
|
||||||
address={message?.sender}
|
address={message?.sender}
|
||||||
name={message?.senderName}
|
name={message?.senderName}
|
||||||
>
|
>
|
||||||
|
|
||||||
<Avatar
|
<Avatar
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: "#27282c",
|
backgroundColor: "#27282c",
|
||||||
@ -97,7 +153,34 @@ export const MessageItem = ({
|
|||||||
>
|
>
|
||||||
{message?.senderName?.charAt(0)}
|
{message?.senderName?.charAt(0)}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
|
|
||||||
|
|
||||||
</WrapperUserAction>
|
</WrapperUserAction>
|
||||||
|
<ClickAwayListener onClickAway={handleTooltipClose}>
|
||||||
|
<div>
|
||||||
|
<Tooltip
|
||||||
|
onClose={handleTooltipClose}
|
||||||
|
open={open}
|
||||||
|
disableFocusListener
|
||||||
|
disableHoverListener
|
||||||
|
disableTouchListener
|
||||||
|
title={`level ${userInfo?.level}`}
|
||||||
|
slotProps={{
|
||||||
|
popper: {
|
||||||
|
disablePortal: true,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
<img onClick={handleTooltipOpen} style={{
|
||||||
|
visibility: userInfo?.level !== undefined ? 'visible' : 'hidden',
|
||||||
|
width: '30px',
|
||||||
|
height: 'auto'
|
||||||
|
}} src={getBadgeImg(userInfo?.level)} />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</ClickAwayListener>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
|
@ -98,7 +98,7 @@ import { formatEmailDate } from "./QMailMessages";
|
|||||||
import { useHandleMobileNativeBack } from "../../hooks/useHandleMobileNativeBack";
|
import { useHandleMobileNativeBack } from "../../hooks/useHandleMobileNativeBack";
|
||||||
import { AdminSpace } from "../Chat/AdminSpace";
|
import { AdminSpace } from "../Chat/AdminSpace";
|
||||||
import { useSetRecoilState } from "recoil";
|
import { useSetRecoilState } from "recoil";
|
||||||
import { selectedGroupIdAtom } from "../../atoms/global";
|
import { addressInfoControllerAtom, selectedGroupIdAtom } from "../../atoms/global";
|
||||||
import { sortArrayByTimestampAndGroupName } from "../../utils/time";
|
import { sortArrayByTimestampAndGroupName } from "../../utils/time";
|
||||||
|
|
||||||
// let touchStartY = 0;
|
// let touchStartY = 0;
|
||||||
@ -494,6 +494,7 @@ export const Group = ({
|
|||||||
const [isOpenSideViewGroups, setIsOpenSideViewGroups] = useState(false)
|
const [isOpenSideViewGroups, setIsOpenSideViewGroups] = useState(false)
|
||||||
const [isForceShowCreationKeyPopup, setIsForceShowCreationKeyPopup] = useState(false)
|
const [isForceShowCreationKeyPopup, setIsForceShowCreationKeyPopup] = useState(false)
|
||||||
const [groupsProperties, setGroupsProperties] = useState({})
|
const [groupsProperties, setGroupsProperties] = useState({})
|
||||||
|
const setUserInfoForLevels = useSetRecoilState(addressInfoControllerAtom);
|
||||||
|
|
||||||
const isPrivate = useMemo(()=> {
|
const isPrivate = useMemo(()=> {
|
||||||
if(!selectedGroup?.groupId || !groupsProperties[selectedGroup?.groupId]) return null
|
if(!selectedGroup?.groupId || !groupsProperties[selectedGroup?.groupId]) return null
|
||||||
@ -1945,6 +1946,7 @@ export const Group = ({
|
|||||||
setTriedToFetchSecretKey(false);
|
setTriedToFetchSecretKey(false);
|
||||||
setNewChat(false);
|
setNewChat(false);
|
||||||
setSelectedGroup(null);
|
setSelectedGroup(null);
|
||||||
|
setUserInfoForLevels({})
|
||||||
setSecretKey(null);
|
setSecretKey(null);
|
||||||
lastFetchedSecretKey.current = null;
|
lastFetchedSecretKey.current = null;
|
||||||
setSecretKeyPublishDate(null);
|
setSecretKeyPublishDate(null);
|
||||||
|
36
src/components/Group/useHandleUserInfo.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { getBaseApiReact } from "../../App";
|
||||||
|
import { useRecoilState, useSetRecoilState } from "recoil";
|
||||||
|
import { addressInfoControllerAtom } from "../../atoms/global";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const useHandleUserInfo = () => {
|
||||||
|
const [userInfo, setUserInfo] = useRecoilState(addressInfoControllerAtom);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const getIndividualUserInfo = useCallback(async (address)=> {
|
||||||
|
try {
|
||||||
|
if(!address || userInfo[address]) return
|
||||||
|
const url = `${getBaseApiReact()}/addresses/${address}`;
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("network error");
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
setUserInfo((prev)=> {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
[address]: data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
//error
|
||||||
|
}
|
||||||
|
}, [userInfo])
|
||||||
|
|
||||||
|
return {
|
||||||
|
getIndividualUserInfo,
|
||||||
|
};
|
||||||
|
};
|