3
0
mirror of https://github.com/Qortal/q-tube.git synced 2025-02-11 17:55:51 +00:00

global superlikes

This commit is contained in:
PhilReact 2023-12-15 14:01:10 +02:00
parent a0e1c65900
commit 8db8bd9dd2
8 changed files with 334 additions and 81 deletions

View File

@ -0,0 +1,19 @@
import { Box, Typography } from '@mui/material'
import React from 'react'
import ListSuperLikes from './ListSuperLikes'
import { useSelector } from 'react-redux'
import { RootState } from '../../../state/store'
export const LiskSuperLikeContainer = () => {
const superlikelist = useSelector((state: RootState) => state.global.superlikelistAll);
return (
<Box>
<Typography sx={{
fontSize: '18px',
color: 'gold'
}}>Recent Super likes</Typography>
<ListSuperLikes superlikes={superlikelist} />
</Box>
)
}

View File

@ -0,0 +1,152 @@
import * as React from "react";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import Divider from "@mui/material/Divider";
import ListItemText from "@mui/material/ListItemText";
import ListItemAvatar from "@mui/material/ListItemAvatar";
import Avatar from "@mui/material/Avatar";
import Typography from "@mui/material/Typography";
import ThumbUpIcon from "@mui/icons-material/ThumbUp";
import { Box } from "@mui/material";
import { useSelector } from "react-redux";
import { RootState } from "../../../state/store";
import { useNavigate } from "react-router-dom";
import EmojiEventsIcon from '@mui/icons-material/EmojiEvents';
const truncateMessage = (message) => {
return message.length > 40 ? message.slice(0, 40) + "..." : message;
};
export default function ListSuperLikes({ superlikes }) {
const hashMapSuperlikes = useSelector(
(state: RootState) => state.video.hashMapSuperlikes
);
const navigate = useNavigate();
return (
<List sx={{ width: "100%", maxWidth: 360, bgcolor: "background.paper" }}>
{superlikes?.map((superlike, index) => {
// let hasHash = false
let message = "";
let url = "";
let forName = ""
// let hash = {}
if (hashMapSuperlikes[superlike?.identifier]) {
message = hashMapSuperlikes[superlike?.identifier]?.comment || "";
if (
hashMapSuperlikes[superlike?.identifier]?.notificationInformation
) {
const info =
hashMapSuperlikes[superlike?.identifier]?.notificationInformation;
forName = info?.name
url = `/video/${info?.name}/${info?.identifier}`;
}
// hasHash = true
// hash = hashMapSuperlikes[superlike?.identifier]
}
let amount = null;
if (!isNaN(parseFloat(superlike?.amount))) {
amount = parseFloat(superlike?.amount).toFixed(2);
}
return (
<>
<ListItem
key={superlike?.identifier}
alignItems="flex-start"
sx={{
cursor: url ? "pointer" : "default",
minHeight: '130px'
}}
onClick={async () => {
if (url) {
navigate(url);
}
}}
>
<Box>
<ListItem
sx={{
padding: '0px'
}}
alignItems="flex-start"
>
<ListItemAvatar>
<Avatar
alt="Remy Sharp"
src={`/arbitrary/THUMBNAIL/${superlike?.name}/qortal_avatar`}
/>
</ListItemAvatar>
<ListItemText
primary={
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "5px",
fontSize: "16px",
}}
>
<ThumbUpIcon
style={{
color: "gold",
}}
/>
<Typography
sx={{
fontSize: "18px",
}}
>
{amount ? amount : ""} QORT
</Typography>
</Box>
}
secondary={
<Box sx={{
fontSize: '15px'
}}>
<Typography
sx={{ display: "inline" }}
component="span"
variant="body2"
color="text.primary"
>
{superlike?.name}
</Typography>
{` - ${truncateMessage(message)}`}
</Box>
}
/>
</ListItem>
{forName && (
<Box sx={{
display: 'flex',
alignItems: 'center',
fontSize: '17px',
gap: '10px',
justifyContent: 'flex-end'
}}>
<EmojiEventsIcon />
{forName}
</Box>
)}
</Box>
</ListItem>
<Box
sx={{
width: "100%",
}}
>
{superlikes.length === index + 1 ? null : <Divider />}
</Box>
</>
);
})}
</List>
);
}

View File

@ -14,7 +14,7 @@ import moment from 'moment'
const generalLocal = localForage.createInstance({
name: "q-tube-general",
});
function extractIdValue(metadescription) {
export function extractIdValue(metadescription) {
// Function to extract the substring within double asterisks
function extractSubstring(str) {
const match = str.match(/\*\*(.*?)\*\*/);

View File

@ -12,6 +12,39 @@ export const VideoContainer = styled(Grid)(({ theme }) => ({
width: '100%'
}));
// export const VideoCardContainer = styled(Grid)({
// display: "flex",
// flexWrap: "wrap",
// padding: "5px 35px 15px 35px",
// });
// export const VideoCardCol = styled(Grid)(({ theme }) => ({
// display: "flex",
// gap: 1,
// alignItems: "center",
// width: "auto",
// position: "relative",
// maxWidth: "100%",
// flexGrow: 1,
// [theme.breakpoints.down("sm")]: {
// width: "100%",
// },
// }));
export const VideoCardContainer = styled('div')(({ theme }) => ({
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))',
gap: theme.spacing(2),
padding: '10px',
width: '100%'
}));
export const VideoCardCol = styled('div')({
minWidth: '250px', // Minimum width of each item
maxWidth: '1fr', // Maximum width, allowing the item to fill the column
// ... other styles
});
export const StoresRow = styled(Grid)(({ theme }) => ({
display: "flex",
flexDirection: "column",
@ -30,7 +63,7 @@ export const VideoCard = styled(Grid)(({ theme }) => ({
display: "flex",
flexDirection: "column",
height: "320px",
width: '300px',
// width: '300px',
backgroundColor: theme.palette.background.paper,
borderRadius: "8px",
padding: "10px 15px",
@ -177,6 +210,16 @@ export const MyStoresCheckbox = styled(Checkbox)(({ theme }) => ({
}
}));
export const ProductManagerRow = styled(Box)(({ theme }) => ({
display: "grid",
gridTemplateColumns: "1fr auto",
alignItems: "center",
justifyContent: "flex-end",
width: "100%",
}));
export const FiltersCol = styled(Grid)(({ theme }) => ({
display: "flex",
flexDirection: "column",

View File

@ -33,6 +33,9 @@ import {
FiltersTitle,
IconsBox,
NameContainer,
VideoCardCol,
ProductManagerRow,
VideoCardContainer,
VideoCard,
VideoCardName,
VideoCardTitle,
@ -60,6 +63,7 @@ import { Playlists } from "../../components/Playlists/Playlists";
import { PlaylistSVG } from "../../assets/svgs/PlaylistSVG";
import BlockIcon from "@mui/icons-material/Block";
import EditIcon from '@mui/icons-material/Edit';
import { LiskSuperLikeContainer } from "../../components/common/ListSuperLikes/LiskSuperLikeContainer";
interface VideoListProps {
mode?: string;
@ -286,7 +290,7 @@ export const VideoList = ({ mode }: VideoListProps) => {
return (
<Grid container sx={{ width: "100%" }}>
<FiltersCol item xs={12} md={2} sm={3}>
<FiltersCol item xs={12} md={2} lg={2} xl={2} sm={3} >
<FiltersContainer>
<Input
id="standard-adornment-name"
@ -506,7 +510,8 @@ export const VideoList = ({ mode }: VideoListProps) => {
</Button>
</FiltersContainer>
</FiltersCol>
<Grid item xs={12} md={10} sm={9}>
<Grid item xs={12} md={10} lg={7} xl={8} sm={9}>
<ProductManagerRow>
<Box
sx={{
width: "100%",
@ -524,41 +529,10 @@ export const VideoList = ({ mode }: VideoListProps) => {
maxWidth: "1400px",
}}
>
{/* <Subtitle sx={{
textAlign: 'start',
fontSize: '18px'
}}>
{!isFiltering ? 'Recently Published Videos': 'Search'}
</Subtitle> */}
</SubtitleContainer>
{/* { countNewVideos > 0 && !isFiltering && (
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography>
{countNewVideos === 1
? `There is ${countNewVideos} new video`
: `There are ${countNewVideos} new videos`}
</Typography>
<Button
sx={{
backgroundColor: theme.palette.primary.light,
color: theme.palette.text.primary,
fontFamily: 'Arial'
}}
onClick={()=> {
getNewVideos()
}}
>
Load new Posts
</Button>
</Box>
)} */}
<VideoContainer>
<VideoCardContainer >
{videos.map((video: any, index: number) => {
const existingVideo = hashMapVideos[video?.id];
let hasHash = false;
@ -581,17 +555,8 @@ export const VideoList = ({ mode }: VideoListProps) => {
if (isPlaylist) {
return (
<Box
sx={{
display: "flex",
flex: 0,
alignItems: "center",
width: "auto",
position: "relative",
" @media (max-width: 450px)": {
width: "100%",
},
}}
<VideoCardCol
onMouseEnter={() => setShowIcons(videoObj.id)}
onMouseLeave={() => setShowIcons(null)}
key={videoObj.id}
@ -686,22 +651,13 @@ export const VideoList = ({ mode }: VideoListProps) => {
</Box>
</BottomParent>
</VideoCard>
</Box>
</VideoCardCol>
);
}
return (
<Box
sx={{
display: "flex",
flex: 0,
alignItems: "center",
width: "auto",
position: "relative",
" @media (max-width: 450px)": {
width: "100%",
},
}}
<VideoCardCol
key={videoObj.id}
onMouseEnter={() => setShowIcons(videoObj.id)}
onMouseLeave={() => setShowIcons(null)}
@ -776,17 +732,21 @@ export const VideoList = ({ mode }: VideoListProps) => {
)}
</BottomParent>
</VideoCard>
</Box>
</VideoCardCol>
);
})}
</VideoContainer>
</VideoCardContainer>
<LazyLoad
onLoadMore={getVideosHandler}
isLoading={isLoading}
></LazyLoad>
</Box>
</ProductManagerRow>
</Grid>
<FiltersCol item xs={0} lg={3} xl={2}>
<LiskSuperLikeContainer />
</FiltersCol>
</Grid>
);
};

View File

@ -11,7 +11,7 @@ import {
} from '@mui/material'
import { useFetchVideos } from '../../hooks/useFetchVideos'
import LazyLoad from '../../components/common/LazyLoad'
import { BottomParent, NameContainer, VideoCard, VideoCardName, VideoCardTitle, VideoContainer, VideoUploadDate } from './VideoList-styles'
import { BottomParent, NameContainer, ProductManagerRow, VideoCard, VideoCardCol, VideoCardContainer, VideoCardName, VideoCardTitle, VideoContainer, VideoUploadDate } from './VideoList-styles'
import ResponsiveImage from '../../components/ResponsiveImage'
import { formatDate, formatTimestampSeconds } from '../../utils/time'
import { Video } from '../../state/features/videoSlice'
@ -130,6 +130,7 @@ export const VideoListComponentLevel = ({ mode }: VideoListProps) => {
return (
<ProductManagerRow>
<Box sx={{
width: '100%',
display: 'flex',
@ -137,7 +138,7 @@ export const VideoListComponentLevel = ({ mode }: VideoListProps) => {
alignItems: 'center'
}}>
<VideoContainer>
<VideoCardContainer>
{videos.map((video: any, index: number) => {
const existingVideo = hashMapVideos[video.id]
let hasHash = false
@ -158,17 +159,8 @@ export const VideoListComponentLevel = ({ mode }: VideoListProps) => {
return (
<Box
sx={{
display: 'flex',
flex: 0,
alignItems: 'center',
width: 'auto',
position: 'relative',
' @media (max-width: 450px)': {
width: '100%'
}
}}
<VideoCardCol
key={videoObj.id}
>
@ -193,12 +185,13 @@ export const VideoListComponentLevel = ({ mode }: VideoListProps) => {
</VideoCard>
</Box>
</VideoCardCol>
)
})}
</VideoContainer>
</VideoCardContainer>
<LazyLoad onLoadMore={getVideosHandler} isLoading={isLoading}></LazyLoad>
</Box>
</ProductManagerRow>
)
}

View File

@ -7,13 +7,15 @@ interface GlobalState {
userAvatarHash: Record<string, string>
publishNames: string[] | null
videoPlaying: any | null
superlikelistAll: any[]
}
const initialState: GlobalState = {
isLoadingGlobal: false,
downloads: {},
userAvatarHash: {},
publishNames: null,
videoPlaying: null
videoPlaying: null,
superlikelistAll: []
}
export const globalSlice = createSlice({
@ -47,6 +49,9 @@ export const globalSlice = createSlice({
setVideoPlaying: (state, action) => {
state.videoPlaying = action.payload
},
setSuperlikesAll: (state, action) => {
state.superlikelistAll = action.payload
},
}
})
@ -56,7 +61,8 @@ export const {
updateDownloads,
setUserAvatarHash,
addPublishNames,
setVideoPlaying
setVideoPlaying,
setSuperlikesAll
} = globalSlice.actions
export default globalSlice.reducer

View File

@ -11,13 +11,16 @@ import { addUser } from "../state/features/authSlice";
import NavBar from "../components/layout/Navbar/Navbar";
import PageLoader from "../components/common/PageLoader";
import { RootState } from "../state/store";
import { setUserAvatarHash } from "../state/features/globalSlice";
import { setSuperlikesAll, setUserAvatarHash } from "../state/features/globalSlice";
import { VideoPlayerGlobal } from "../components/common/VideoPlayerGlobal";
import { Rnd } from "react-rnd";
import { RequestQueue } from "../utils/queue";
import { EditVideo } from "../components/EditVideo/EditVideo";
import { EditPlaylist } from "../components/EditPlaylist/EditPlaylist";
import ConsentModal from "../components/common/ConsentModal";
import { SUPER_LIKE_BASE, minPriceSuperlike } from "../constants";
import { extractSigValue, getPaymentInfo, isTimestampWithinRange } from "../pages/VideoContent/VideoContent";
import { useFetchSuperLikes } from "../hooks/useFetchSuperLikes";
interface Props {
children: React.ReactNode;
@ -35,6 +38,9 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
const isDragging = useRef(false);
const [userAvatar, setUserAvatar] = useState<string>("");
const user = useSelector((state: RootState) => state.auth.user);
const {addSuperlikeRawDataGetToList} = useFetchSuperLikes()
const interval = useRef<any>(null)
const videoPlaying = useSelector(
(state: RootState) => state.global.videoPlaying
);
@ -128,6 +134,80 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
return isDragging.current;
}, []);
const getSuperlikes = useCallback(
async () => {
try {
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&query=${SUPER_LIKE_BASE}&limit=20&includemetadata=true&reverse=true&excludeblocked=true`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const responseData = await response.json();
let comments: any[] = [];
for (const comment of responseData) {
if (comment.identifier && comment.name && comment?.metadata?.description) {
try {
const result = extractSigValue(comment?.metadata?.description)
if(!result) continue
const res = await getPaymentInfo(result);
if(+res?.amount >= minPriceSuperlike && isTimestampWithinRange(res?.timestamp, comment.created)){
addSuperlikeRawDataGetToList({name:comment.name, identifier:comment.identifier, content: comment})
comments = [...comments, {
...comment,
message: "",
amount: res.amount
}];
}
} catch (error) {
}
}
}
dispatch(setSuperlikesAll(comments));
} catch (error) {
console.error(error);
} finally {
}
},
[]
);
const checkSuperlikes = useCallback(
() => {
let isCalling = false
interval.current = setInterval(async () => {
if (isCalling) return
isCalling = true
const res = await getSuperlikes()
isCalling = false
}, 300000)
getSuperlikes()
},
[getSuperlikes])
useEffect(() => {
checkSuperlikes();
}, [checkSuperlikes]);
return (
<>
{isLoadingGlobal && <PageLoader />}