mirror of
https://github.com/Qortal/q-tube.git
synced 2025-02-11 17:55:51 +00:00
finished super likes
This commit is contained in:
parent
d8e2ecb382
commit
a0e1c65900
@ -366,7 +366,12 @@ export const EditPlaylist = () => {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
dispatch(
|
||||
setNotification({
|
||||
msg: "Playlist published",
|
||||
alertType: "success",
|
||||
})
|
||||
);
|
||||
|
||||
onClose();
|
||||
} catch (error: any) {
|
||||
@ -394,21 +399,7 @@ export const EditPlaylist = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleOnchange = (index: number, type: string, value: string) => {
|
||||
// setFiles((prev) => {
|
||||
// let formattedValue = value
|
||||
// console.log({type})
|
||||
// if(type === 'title'){
|
||||
// formattedValue = value.replace(/[^a-zA-Z0-9\s]/g, "")
|
||||
// }
|
||||
// const copyFiles = [...prev];
|
||||
// copyFiles[index] = {
|
||||
// ...copyFiles[index],
|
||||
// [type]: formattedValue,
|
||||
// };
|
||||
// return copyFiles;
|
||||
// });
|
||||
};
|
||||
|
||||
|
||||
const handleOptionCategoryChangeVideos = (
|
||||
event: SelectChangeEvent<string>
|
||||
|
@ -115,7 +115,7 @@ export const Notifications = () => {
|
||||
let urlReference = null
|
||||
try {
|
||||
let idForUrl = extractIdValue(comment?.metadata?.description)
|
||||
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${idForUrl}&limit=1&includemetadata=false&reverse=true&excludeblocked=true&offset=0&name=${username}`;
|
||||
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${idForUrl}&limit=1&includemetadata=false&reverse=false&excludeblocked=true&offset=0&name=${username}`;
|
||||
const response2 = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
|
@ -19,7 +19,13 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import { setNotification } from "../../../state/features/notificationsSlice";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import { objectToBase64 } from "../../../utils/toBase64";
|
||||
import { FOR, FOR_SUPER_LIKE, QTUBE_VIDEO_BASE, SUPER_LIKE_BASE, minPriceSuperlike } from "../../../constants";
|
||||
import {
|
||||
FOR,
|
||||
FOR_SUPER_LIKE,
|
||||
QTUBE_VIDEO_BASE,
|
||||
SUPER_LIKE_BASE,
|
||||
minPriceSuperlike,
|
||||
} from "../../../constants";
|
||||
import { CommentInput } from "../Comments/Comments-styles";
|
||||
import {
|
||||
CrowdfundActionButton,
|
||||
@ -33,7 +39,14 @@ import { RootState } from "../../../state/store";
|
||||
|
||||
const uid = new ShortUniqueId({ length: 4 });
|
||||
|
||||
export const SuperLike = ({ onSuccess, name, service, identifier, totalAmount, numberOfSuperlikes }) => {
|
||||
export const SuperLike = ({
|
||||
onSuccess,
|
||||
name,
|
||||
service,
|
||||
identifier,
|
||||
totalAmount,
|
||||
numberOfSuperlikes,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [amount, setAmount] = useState<number>(10);
|
||||
const [comment, setComment] = useState<string>("");
|
||||
@ -56,8 +69,8 @@ export const SuperLike = ({ onSuccess, name, service, identifier, totalAmount, n
|
||||
|
||||
async function publishSuperLike() {
|
||||
try {
|
||||
if(!username) throw new Error("You need a name to publish")
|
||||
if(!name) throw new Error("Could not retrieve content creator's name");
|
||||
if (!username) throw new Error("You need a name to publish");
|
||||
if (!name) throw new Error("Could not retrieve content creator's name");
|
||||
|
||||
let resName = await qortalRequest({
|
||||
action: "GET_NAME_DATA",
|
||||
@ -65,13 +78,15 @@ export const SuperLike = ({ onSuccess, name, service, identifier, totalAmount, n
|
||||
});
|
||||
|
||||
const address = resName.owner;
|
||||
if(!identifier) throw new Error("Could not retrieve id of video post");
|
||||
if (comment.length > 200) throw new Error("Comment needs to be under 200 characters")
|
||||
if (!identifier) throw new Error("Could not retrieve id of video post");
|
||||
// if (comment.length > 200) throw new Error("Comment needs to be under 200 characters")
|
||||
|
||||
if (!address)
|
||||
throw new Error("Could not retrieve content creator's address");
|
||||
if (!amount || amount < minPriceSuperlike)
|
||||
throw new Error(`The amount needs to be at least ${minPriceSuperlike} QORT`);
|
||||
throw new Error(
|
||||
`The amount needs to be at least ${minPriceSuperlike} QORT`
|
||||
);
|
||||
|
||||
let listOfPublishes = [];
|
||||
|
||||
@ -82,9 +97,12 @@ export const SuperLike = ({ onSuccess, name, service, identifier, totalAmount, n
|
||||
amount: amount,
|
||||
});
|
||||
|
||||
|
||||
|
||||
let metadescription = `**sig:${res.signature};${FOR}:${name}_${FOR_SUPER_LIKE};nm:${name.slice(0,20)};id:${identifier.slice(-30)}**`;
|
||||
let metadescription = `**sig:${
|
||||
res.signature
|
||||
};${FOR}:${name}_${FOR_SUPER_LIKE};nm:${name.slice(
|
||||
0,
|
||||
20
|
||||
)};id:${identifier.slice(-30)}**`;
|
||||
|
||||
const id = uid();
|
||||
const identifierSuperLike = `${SUPER_LIKE_BASE}${identifier.slice(
|
||||
@ -92,14 +110,25 @@ export const SuperLike = ({ onSuccess, name, service, identifier, totalAmount, n
|
||||
39
|
||||
)}_${id}`;
|
||||
|
||||
const superLikeToBase64 = await objectToBase64({
|
||||
comment,
|
||||
transactionReference: res.signature,
|
||||
notificationInformation: {
|
||||
name,
|
||||
identifier,
|
||||
for: `${name}_${FOR_SUPER_LIKE}`,
|
||||
},
|
||||
about:
|
||||
"Super likes are a way to suppert your favorite content creators. Attach a message to the Super like and have your message seen before normal comments. There is a minimum amount for a Super like. Each Super like is verified before displaying to make there aren't any non-paid Super likes",
|
||||
});
|
||||
// Description is obtained from raw data
|
||||
const base64 = utf8ToBase64(comment);
|
||||
// const base64 = utf8ToBase64(comment);
|
||||
|
||||
const requestBodyJson: any = {
|
||||
action: "PUBLISH_QDN_RESOURCE",
|
||||
name: username,
|
||||
service: "BLOG_COMMENT",
|
||||
data64: base64,
|
||||
data64: superLikeToBase64,
|
||||
title: "",
|
||||
description: metadescription,
|
||||
identifier: identifierSuperLike,
|
||||
@ -107,7 +136,6 @@ export const SuperLike = ({ onSuccess, name, service, identifier, totalAmount, n
|
||||
filename: `superlike_metadata.json`,
|
||||
};
|
||||
|
||||
|
||||
listOfPublishes.push(requestBodyJson);
|
||||
|
||||
setPublishes(listOfPublishes);
|
||||
@ -140,47 +168,63 @@ export const SuperLike = ({ onSuccess, name, service, identifier, totalAmount, n
|
||||
<>
|
||||
<Box
|
||||
onClick={() => {
|
||||
if (username === name) {
|
||||
dispatch(
|
||||
setNotification({
|
||||
msg: "You cannot send yourself a Super like",
|
||||
alertType: "error",
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
setIsOpen(true);
|
||||
}}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '15px',
|
||||
cursor: 'pointer',
|
||||
flexShrink: 0
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "15px",
|
||||
cursor: "pointer",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{numberOfSuperlikes === 0 ? null : (
|
||||
<p style={{
|
||||
fontSize: '16px',
|
||||
userSelect: 'none',
|
||||
margin: '0px',
|
||||
padding: '0px'
|
||||
}}>{totalAmount} QORT from {numberOfSuperlikes} Super Likes</p>
|
||||
)}
|
||||
|
||||
<Tooltip title="Super Like" placement="top">
|
||||
<Box sx={{
|
||||
padding: '5px',
|
||||
borderRadius: '7px',
|
||||
gap: '10px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
outline: '1px gold solid'
|
||||
}}>
|
||||
|
||||
<ThumbUpIcon
|
||||
style={{
|
||||
color: "gold",
|
||||
|
||||
|
||||
}}
|
||||
/>
|
||||
<p style={{
|
||||
fontSize: '16px',
|
||||
margin: '0px'
|
||||
}}>Super Like</p>
|
||||
</Box>
|
||||
>
|
||||
{numberOfSuperlikes === 0 ? null : (
|
||||
<p
|
||||
style={{
|
||||
fontSize: "16px",
|
||||
userSelect: "none",
|
||||
margin: "0px",
|
||||
padding: "0px",
|
||||
}}
|
||||
>
|
||||
{totalAmount} QORT from {numberOfSuperlikes} Super Likes
|
||||
</p>
|
||||
)}
|
||||
|
||||
<Tooltip title="Super Like" placement="top">
|
||||
<Box
|
||||
sx={{
|
||||
padding: "5px",
|
||||
borderRadius: "7px",
|
||||
gap: "10px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
outline: "1px gold solid",
|
||||
}}
|
||||
>
|
||||
<ThumbUpIcon
|
||||
style={{
|
||||
color: "gold",
|
||||
}}
|
||||
/>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "16px",
|
||||
margin: "0px",
|
||||
}}
|
||||
>
|
||||
Super Like
|
||||
</p>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Modal
|
||||
@ -190,12 +234,12 @@ export const SuperLike = ({ onSuccess, name, service, identifier, totalAmount, n
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<ModalBody>
|
||||
<Box
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: '100%'
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<NewCrowdfundTitle>Super Like</NewCrowdfundTitle>
|
||||
@ -210,7 +254,7 @@ export const SuperLike = ({ onSuccess, name, service, identifier, totalAmount, n
|
||||
>
|
||||
<Box>
|
||||
<InputLabel htmlFor="standard-adornment-amount">
|
||||
Amount in QORT
|
||||
Amount in QORT (min 10 QORT)
|
||||
</InputLabel>
|
||||
<Input
|
||||
id="standard-adornment-amount"
|
||||
@ -241,7 +285,7 @@ export const SuperLike = ({ onSuccess, name, service, identifier, totalAmount, n
|
||||
variant="filled"
|
||||
value={comment}
|
||||
inputProps={{
|
||||
maxLength: 200,
|
||||
maxLength: 500,
|
||||
}}
|
||||
InputLabelProps={{ style: { fontSize: "18px" } }}
|
||||
onChange={(e) => setComment(e.target.value)}
|
||||
@ -284,12 +328,12 @@ export const SuperLike = ({ onSuccess, name, service, identifier, totalAmount, n
|
||||
isOpen={isOpenMultiplePublish}
|
||||
onSubmit={() => {
|
||||
onSuccess({
|
||||
name: username,
|
||||
message: comment,
|
||||
service,
|
||||
identifier,
|
||||
amount: +amount,
|
||||
created: Date.now()
|
||||
name: username,
|
||||
message: comment,
|
||||
service,
|
||||
identifier,
|
||||
amount: +amount,
|
||||
created: Date.now(),
|
||||
});
|
||||
setIsOpenMultiplePublish(false);
|
||||
onClose();
|
||||
@ -300,7 +344,6 @@ export const SuperLike = ({ onSuccess, name, service, identifier, totalAmount, n
|
||||
alertType: "success",
|
||||
})
|
||||
);
|
||||
|
||||
}}
|
||||
publishes={publishes}
|
||||
/>
|
||||
|
@ -37,13 +37,17 @@ interface CommentProps {
|
||||
postName: string;
|
||||
onSubmit: (obj?: any, isEdit?: boolean) => void;
|
||||
amount?: null | number
|
||||
isSuperLike?: boolean
|
||||
hasHash?: boolean
|
||||
}
|
||||
export const Comment = ({
|
||||
comment,
|
||||
postId,
|
||||
postName,
|
||||
onSubmit,
|
||||
amount
|
||||
amount,
|
||||
isSuperLike,
|
||||
hasHash
|
||||
}: CommentProps) => {
|
||||
const [isReplying, setIsReplying] = useState<boolean>(false);
|
||||
const [isEditing, setIsEditing] = useState<boolean>(false);
|
||||
@ -90,6 +94,9 @@ export const Comment = ({
|
||||
isEdit
|
||||
commentId={currentEdit?.identifier}
|
||||
commentMessage={currentEdit?.message}
|
||||
isSuperLike={!!currentEdit?.transactionReference}
|
||||
comment={comment}
|
||||
hasHash={hasHash}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
@ -137,7 +144,7 @@ export const Comment = ({
|
||||
>
|
||||
reply
|
||||
</CommentActionButton>
|
||||
{user?.name === comment?.name && (
|
||||
{user?.name === comment?.name && hasHash && (
|
||||
<CommentActionButton
|
||||
size="small"
|
||||
variant="contained"
|
||||
|
@ -4,7 +4,7 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import { RootState } from "../../../state/store";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import { setNotification } from "../../../state/features/notificationsSlice";
|
||||
import { toBase64 } from "../../../utils/toBase64";
|
||||
import { objectToBase64, toBase64 } from "../../../utils/toBase64";
|
||||
import localforage from "localforage";
|
||||
import {
|
||||
CommentInput,
|
||||
@ -12,6 +12,7 @@ import {
|
||||
SubmitCommentButton,
|
||||
} from "./Comments-styles";
|
||||
import { COMMENT_BASE } from "../../../constants";
|
||||
import { addtoHashMapSuperlikes } from "../../../state/features/videoSlice";
|
||||
const uid = new ShortUniqueId();
|
||||
|
||||
const notification = localforage.createInstance({
|
||||
@ -83,6 +84,9 @@ interface CommentEditorProps {
|
||||
commentId?: string;
|
||||
isEdit?: boolean;
|
||||
commentMessage?: string;
|
||||
isSuperLike?: boolean
|
||||
comment?: any;
|
||||
hasHash?: boolean
|
||||
}
|
||||
|
||||
export function utf8ToBase64(inputString: string): string {
|
||||
@ -104,7 +108,10 @@ export const CommentEditor = ({
|
||||
isReply,
|
||||
commentId,
|
||||
isEdit,
|
||||
commentMessage
|
||||
commentMessage,
|
||||
isSuperLike,
|
||||
comment,
|
||||
hasHash
|
||||
}: CommentEditorProps) => {
|
||||
const [value, setValue] = useState<string>("");
|
||||
const dispatch = useDispatch();
|
||||
@ -149,13 +156,34 @@ export const CommentEditor = ({
|
||||
}
|
||||
|
||||
try {
|
||||
let data64 = null
|
||||
let description = ""
|
||||
let tag1 = ""
|
||||
let superObj = {}
|
||||
if(isSuperLike){
|
||||
if(!comment?.metadata?.description || !comment?.metadata?.tags[0] || !comment?.transactionReference || !comment?.notificationInformation || !comment?.about) throw new Error('unable to edit Super like')
|
||||
description = comment?.metadata?.description
|
||||
tag1 = comment?.metadata?.tags[0]
|
||||
superObj = {
|
||||
comment: value,
|
||||
transactionReference: comment.transactionReference,
|
||||
notificationInformation: comment.notificationInformation,
|
||||
about: comment.about
|
||||
}
|
||||
const superLikeToBase64 = await objectToBase64(superObj);
|
||||
data64 = superLikeToBase64
|
||||
}
|
||||
if(isSuperLike && !data64) throw new Error('unable to edit Super like')
|
||||
|
||||
const base64 = utf8ToBase64(value);
|
||||
const resourceResponse = await qortalRequest({
|
||||
action: "PUBLISH_QDN_RESOURCE",
|
||||
name: name,
|
||||
service: "BLOG_COMMENT",
|
||||
data64: base64,
|
||||
data64: isSuperLike ? data64 : base64,
|
||||
identifier: identifier,
|
||||
description,
|
||||
tag1
|
||||
});
|
||||
dispatch(
|
||||
setNotification({
|
||||
@ -163,6 +191,15 @@ export const CommentEditor = ({
|
||||
alertType: "success",
|
||||
})
|
||||
);
|
||||
|
||||
if(isSuperLike){
|
||||
dispatch(addtoHashMapSuperlikes({
|
||||
...superObj,
|
||||
...comment,
|
||||
message: value
|
||||
}))
|
||||
|
||||
}
|
||||
if (idForNotification) {
|
||||
addItem({
|
||||
id: idForNotification,
|
||||
@ -217,13 +254,18 @@ export const CommentEditor = ({
|
||||
}
|
||||
|
||||
await publishComment(identifier, idForNotification);
|
||||
onSubmit({
|
||||
created: Date.now(),
|
||||
identifier,
|
||||
message: value,
|
||||
service,
|
||||
name: user?.name,
|
||||
});
|
||||
if(isSuperLike){
|
||||
onSubmit({})
|
||||
} else {
|
||||
onSubmit({
|
||||
created: Date.now(),
|
||||
identifier,
|
||||
message: value,
|
||||
service,
|
||||
name: user?.name,
|
||||
});
|
||||
}
|
||||
|
||||
setValue("");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
@ -57,6 +57,9 @@ export const SuperLikesSection = ({ loadingSuperLikes, superlikes, postId, postN
|
||||
const { user } = useSelector((state: RootState) => state.auth);
|
||||
const [newMessages, setNewMessages] = useState(0);
|
||||
const [loadingComments, setLoadingComments] = useState<boolean>(null);
|
||||
const hashMapSuperlikes = useSelector(
|
||||
(state: RootState) => state.video.hashMapSuperlikes
|
||||
)
|
||||
const onSubmit = (obj?: any, isEdit?: boolean) => {
|
||||
if (isEdit) {
|
||||
setListComments((prev: any[]) => {
|
||||
@ -168,7 +171,7 @@ export const SuperLikesSection = ({ loadingSuperLikes, superlikes, postId, postN
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if(superlikes.length > 0 && postId){
|
||||
if(postId){
|
||||
getComments(superlikes, postId)
|
||||
}
|
||||
}, [getComments, superlikes, postId]);
|
||||
@ -188,7 +191,6 @@ export const SuperLikesSection = ({ loadingSuperLikes, superlikes, postId, postN
|
||||
}, []);
|
||||
}, [listComments]);
|
||||
|
||||
console.log({loadingComments, listComments, superlikes})
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -212,14 +214,24 @@ export const SuperLikesSection = ({ loadingSuperLikes, superlikes, postId, postN
|
||||
) : (
|
||||
<CommentContainer>
|
||||
{structuredCommentList.map((comment: any) => {
|
||||
let hasHash = false
|
||||
let message = {...comment}
|
||||
let hash = {}
|
||||
if(hashMapSuperlikes[comment?.identifier]){
|
||||
message.message = hashMapSuperlikes[comment?.identifier]?.comment || ""
|
||||
hasHash = true
|
||||
hash = hashMapSuperlikes[comment?.identifier]
|
||||
}
|
||||
return (
|
||||
<Comment
|
||||
key={comment?.identifier}
|
||||
comment={comment}
|
||||
comment={{...message, ...hash}}
|
||||
onSubmit={onSubmit}
|
||||
postId={postId}
|
||||
postName={postName}
|
||||
amount={comment?.amount || null}
|
||||
isSuperLike
|
||||
hasHash={hasHash}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@ -1,4 +1,4 @@
|
||||
const useTestIdentifiers = true;
|
||||
const useTestIdentifiers = false;
|
||||
|
||||
export const QTUBE_VIDEO_BASE = useTestIdentifiers
|
||||
? "MYTEST_vid_"
|
||||
|
114
src/hooks/useFetchSuperLikes.tsx
Normal file
114
src/hooks/useFetchSuperLikes.tsx
Normal file
@ -0,0 +1,114 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import {
|
||||
|
||||
addtoHashMapSuperlikes
|
||||
} from '../state/features/videoSlice'
|
||||
|
||||
import { RootState } from '../state/store'
|
||||
import { queueSuperlikes } from '../wrappers/GlobalWrapper'
|
||||
|
||||
|
||||
|
||||
export const useFetchSuperLikes = () => {
|
||||
const dispatch = useDispatch()
|
||||
const hashMapSuperlikes = useSelector(
|
||||
(state: RootState) => state.video.hashMapSuperlikes
|
||||
)
|
||||
const checkAndUpdateSuperlike= React.useCallback(
|
||||
(superlike: any) => {
|
||||
const existingVideo = hashMapSuperlikes[superlike.identifier]
|
||||
if (!existingVideo) {
|
||||
return true
|
||||
} else if (
|
||||
superlike?.updated &&
|
||||
existingVideo?.updated &&
|
||||
(!existingVideo?.updated || superlike?.updated) > existingVideo?.updated
|
||||
) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
[hashMapSuperlikes]
|
||||
)
|
||||
|
||||
|
||||
const fetchSuperlike = async (data: any) => {
|
||||
const getsuper = async () => {
|
||||
const { user, videoId, content } = data
|
||||
let obj: any = {
|
||||
...content,
|
||||
isValid: false
|
||||
}
|
||||
|
||||
if (!user || !videoId) return obj
|
||||
|
||||
try {
|
||||
|
||||
const responseData = await qortalRequest({
|
||||
action: 'FETCH_QDN_RESOURCE',
|
||||
name: user,
|
||||
service: content?.service || 'BLOG_COMMENT',
|
||||
identifier: videoId
|
||||
})
|
||||
|
||||
obj = {
|
||||
...content,
|
||||
...responseData,
|
||||
isValid: true
|
||||
}
|
||||
|
||||
return obj
|
||||
} catch (error: any) {
|
||||
throw new Error(error?.message || 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const res = await getsuper()
|
||||
return res
|
||||
}
|
||||
|
||||
|
||||
|
||||
const getSuperLikes = async (user: string, videoId: string, content: any, retries: number = 0) => {
|
||||
try {
|
||||
const res = await fetchSuperlike({
|
||||
user,
|
||||
videoId,
|
||||
content
|
||||
})
|
||||
|
||||
dispatch(addtoHashMapSuperlikes(res))
|
||||
} catch (error) {
|
||||
retries= retries + 1
|
||||
if (retries < 2) { // 3 is the maximum number of retries here, you can adjust it to your needs
|
||||
queueSuperlikes.push(() => getSuperLikes(user, videoId, content, retries + 1));
|
||||
} else {
|
||||
console.error('Failed to get video after 3 attempts', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
const addSuperlikeRawDataGetToList = useCallback(({name, identifier, content})=> {
|
||||
|
||||
if (name && identifier) {
|
||||
const res = checkAndUpdateSuperlike(content)
|
||||
if (res) {
|
||||
queueSuperlikes.push(() => getSuperLikes(name, identifier, content));
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}, [checkAndUpdateSuperlike])
|
||||
|
||||
|
||||
|
||||
return {
|
||||
addSuperlikeRawDataGetToList
|
||||
}
|
||||
}
|
@ -1,4 +1,10 @@
|
||||
import React, { useState, useMemo, useRef, useEffect, useCallback } from "react";
|
||||
import React, {
|
||||
useState,
|
||||
useMemo,
|
||||
useRef,
|
||||
useEffect,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { setIsLoadingGlobal } from "../../state/features/globalSlice";
|
||||
@ -33,25 +39,70 @@ import {
|
||||
CrowdfundSubTitle,
|
||||
CrowdfundSubTitleRow,
|
||||
} from "../../components/UploadVideo/Upload-styles";
|
||||
import { QTUBE_VIDEO_BASE } from "../../constants";
|
||||
import {
|
||||
QTUBE_VIDEO_BASE,
|
||||
SUPER_LIKE_BASE,
|
||||
minPriceSuperlike,
|
||||
} from "../../constants";
|
||||
import { Playlists } from "../../components/Playlists/Playlists";
|
||||
import { DisplayHtml } from "../../components/common/TextEditor/DisplayHtml";
|
||||
import FileElement from "../../components/common/FileElement";
|
||||
import { SuperLike } from "../../components/common/SuperLike/SuperLike";
|
||||
import { useFetchSuperLikes } from "../../hooks/useFetchSuperLikes";
|
||||
import {
|
||||
extractSigValue,
|
||||
getPaymentInfo,
|
||||
isTimestampWithinRange,
|
||||
} from "../VideoContent/VideoContent";
|
||||
import { SuperLikesSection } from "../../components/common/SuperLikesList/SuperLikesSection";
|
||||
|
||||
export const PlaylistContent = () => {
|
||||
const { name, id } = useParams();
|
||||
const [doAutoPlay, setDoAutoPlay] = useState(false)
|
||||
const [doAutoPlay, setDoAutoPlay] = useState(false);
|
||||
const [isExpandedDescription, setIsExpandedDescription] =
|
||||
useState<boolean>(false);
|
||||
const [descriptionHeight, setDescriptionHeight] =
|
||||
useState<null | number>(null);
|
||||
|
||||
const [descriptionHeight, setDescriptionHeight] = useState<null | number>(
|
||||
null
|
||||
);
|
||||
const [superlikeList, setSuperlikelist] = useState<any[]>([]);
|
||||
const [loadingSuperLikes, setLoadingSuperLikes] = useState<boolean>(false);
|
||||
const { addSuperlikeRawDataGetToList } = useFetchSuperLikes();
|
||||
|
||||
const calculateAmountSuperlike = useMemo(() => {
|
||||
const totalQort = superlikeList?.reduce((acc, curr) => {
|
||||
if (curr?.amount && !isNaN(parseFloat(curr.amount)))
|
||||
return acc + parseFloat(curr.amount);
|
||||
else return acc;
|
||||
}, 0);
|
||||
return totalQort?.toFixed(2);
|
||||
}, [superlikeList]);
|
||||
const numberOfSuperlikes = useMemo(() => {
|
||||
return superlikeList?.length ?? 0;
|
||||
}, [superlikeList]);
|
||||
|
||||
const [nameAddress, setNameAddress] = useState<string>("");
|
||||
|
||||
const getAddressName = async (name) => {
|
||||
const response = await qortalRequest({
|
||||
action: "GET_NAME_DATA",
|
||||
name: name,
|
||||
});
|
||||
|
||||
if (response?.owner) {
|
||||
setNameAddress(response.owner);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (name) {
|
||||
getAddressName(name);
|
||||
}
|
||||
}, [name]);
|
||||
|
||||
const userAvatarHash = useSelector(
|
||||
(state: RootState) => state.global.userAvatarHash
|
||||
);
|
||||
const contentRef = useRef(null);
|
||||
|
||||
|
||||
|
||||
const avatarUrl = useMemo(() => {
|
||||
let url = "";
|
||||
@ -144,182 +195,203 @@ export const PlaylistContent = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const checkforPlaylist = React.useCallback(async (name, id) => {
|
||||
const checkforPlaylist = React.useCallback(
|
||||
async (name, id) => {
|
||||
try {
|
||||
dispatch(setIsLoadingGlobal(true));
|
||||
|
||||
if (!name || !id) return;
|
||||
|
||||
const url = `/arbitrary/resources/search?mode=ALL&service=PLAYLIST&identifier=${id}&limit=1&includemetadata=true&reverse=true&excludeblocked=true&name=${name}&exactmatchnames=true&offset=0`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const responseDataSearch = await response.json();
|
||||
|
||||
if (responseDataSearch?.length > 0) {
|
||||
let resourceData = responseDataSearch[0];
|
||||
resourceData = {
|
||||
title: resourceData?.metadata?.title,
|
||||
category: resourceData?.metadata?.category,
|
||||
categoryName: resourceData?.metadata?.categoryName,
|
||||
tags: resourceData?.metadata?.tags || [],
|
||||
description: resourceData?.metadata?.description,
|
||||
created: resourceData?.created,
|
||||
updated: resourceData?.updated,
|
||||
name: resourceData.name,
|
||||
videoImage: "",
|
||||
identifier: resourceData.identifier,
|
||||
service: resourceData.service,
|
||||
};
|
||||
|
||||
const responseData = await qortalRequest({
|
||||
action: "FETCH_QDN_RESOURCE",
|
||||
name: resourceData.name,
|
||||
service: resourceData.service,
|
||||
identifier: resourceData.identifier,
|
||||
});
|
||||
|
||||
if (responseData && !responseData.error) {
|
||||
const combinedData = {
|
||||
...resourceData,
|
||||
...responseData,
|
||||
};
|
||||
const videos = [];
|
||||
if (combinedData?.videos) {
|
||||
for (const vid of combinedData.videos) {
|
||||
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${vid.identifier}&limit=1&includemetadata=true&reverse=true&name=${vid.name}&exactmatchnames=true&offset=0`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const responseDataSearchVid = await response.json();
|
||||
|
||||
if (responseDataSearchVid?.length > 0) {
|
||||
let resourceData2 = responseDataSearchVid[0];
|
||||
videos.push(resourceData2);
|
||||
}
|
||||
}
|
||||
}
|
||||
combinedData.videos = videos;
|
||||
setPlaylistData(combinedData);
|
||||
if (combinedData?.videos?.length > 0) {
|
||||
const vid = combinedData?.videos[0];
|
||||
const existingVideo = hashMapVideos[vid?.identifier];
|
||||
|
||||
if (existingVideo) {
|
||||
setVideoData(existingVideo);
|
||||
} else {
|
||||
getVideoData(vid?.name, vid?.identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
} finally {
|
||||
dispatch(setIsLoadingGlobal(false));
|
||||
}
|
||||
},
|
||||
[hashMapVideos]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (name && id) {
|
||||
checkforPlaylist(name, id);
|
||||
}
|
||||
}, [id, name]);
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current) {
|
||||
const height = contentRef.current.offsetHeight;
|
||||
if (height > 100) {
|
||||
// Assuming 100px is your threshold
|
||||
setDescriptionHeight(100);
|
||||
}
|
||||
}
|
||||
}, [videoData]);
|
||||
|
||||
const nextVideo = useMemo(() => {
|
||||
const currentVideoIndex = playlistData?.videos?.findIndex(
|
||||
(item) => item?.identifier === videoData?.id
|
||||
);
|
||||
if (currentVideoIndex !== -1) {
|
||||
const nextVideoIndex = currentVideoIndex + 1;
|
||||
const findVideo = playlistData?.videos[nextVideoIndex] || null;
|
||||
if (findVideo) {
|
||||
const id = findVideo?.identifier?.replace("_metadata", "");
|
||||
return {
|
||||
...findVideo,
|
||||
service: "VIDEO",
|
||||
identifier: id,
|
||||
jsonId: findVideo?.identifier,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [playlistData, videoData]);
|
||||
|
||||
const onEndVideo = useCallback(() => {
|
||||
const currentVideoIndex = playlistData?.videos?.findIndex(
|
||||
(item) => item?.identifier === videoData?.id
|
||||
);
|
||||
if (currentVideoIndex !== -1) {
|
||||
const nextVideoIndex = currentVideoIndex + 1;
|
||||
const findVideo = playlistData?.videos[nextVideoIndex] || null;
|
||||
if (findVideo) {
|
||||
getVideoData(findVideo?.name, findVideo?.identifier);
|
||||
setDoAutoPlay(true);
|
||||
}
|
||||
}
|
||||
}, [videoData, playlistData]);
|
||||
|
||||
const getComments = useCallback(async (id, nameAddressParam) => {
|
||||
if (!id) return;
|
||||
try {
|
||||
dispatch(setIsLoadingGlobal(true));
|
||||
setLoadingSuperLikes(true);
|
||||
|
||||
if (!name || !id) return;
|
||||
|
||||
const url = `/arbitrary/resources/search?mode=ALL&service=PLAYLIST&identifier=${id}&limit=1&includemetadata=true&reverse=true&excludeblocked=true&name=${name}&exactmatchnames=true&offset=0`;
|
||||
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&query=${SUPER_LIKE_BASE}${id.slice(
|
||||
0,
|
||||
39
|
||||
)}&limit=100&includemetadata=true&reverse=true&excludeblocked=true`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const responseDataSearch = await response.json();
|
||||
|
||||
if (responseDataSearch?.length > 0) {
|
||||
let resourceData = responseDataSearch[0];
|
||||
resourceData = {
|
||||
title: resourceData?.metadata?.title,
|
||||
category: resourceData?.metadata?.category,
|
||||
categoryName: resourceData?.metadata?.categoryName,
|
||||
tags: resourceData?.metadata?.tags || [],
|
||||
description: resourceData?.metadata?.description,
|
||||
created: resourceData?.created,
|
||||
updated: resourceData?.updated,
|
||||
name: resourceData.name,
|
||||
videoImage: "",
|
||||
identifier: resourceData.identifier,
|
||||
service: resourceData.service,
|
||||
};
|
||||
|
||||
const responseData = await qortalRequest({
|
||||
action: "FETCH_QDN_RESOURCE",
|
||||
name: resourceData.name,
|
||||
service: resourceData.service,
|
||||
identifier: resourceData.identifier,
|
||||
});
|
||||
|
||||
if (responseData && !responseData.error) {
|
||||
const combinedData = {
|
||||
...resourceData,
|
||||
...responseData,
|
||||
};
|
||||
const videos = [];
|
||||
if (combinedData?.videos) {
|
||||
for (const vid of combinedData.videos) {
|
||||
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${vid.identifier}&limit=1&includemetadata=true&reverse=true&name=${vid.name}&exactmatchnames=true&offset=0`;
|
||||
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 &&
|
||||
res.recipient === nameAddressParam &&
|
||||
isTimestampWithinRange(res?.timestamp, comment.created)
|
||||
) {
|
||||
addSuperlikeRawDataGetToList({
|
||||
name: comment.name,
|
||||
identifier: comment.identifier,
|
||||
content: comment,
|
||||
});
|
||||
const responseDataSearchVid = await response.json();
|
||||
|
||||
if (responseDataSearchVid?.length > 0) {
|
||||
let resourceData2 = responseDataSearchVid[0];
|
||||
videos.push(resourceData2);
|
||||
}
|
||||
comments = [
|
||||
...comments,
|
||||
{
|
||||
...comment,
|
||||
message: "",
|
||||
amount: res.amount,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
combinedData.videos = videos;
|
||||
setPlaylistData(combinedData);
|
||||
if(combinedData?.videos?.length > 0){
|
||||
const vid = combinedData?.videos[0]
|
||||
const existingVideo = hashMapVideos[vid?.identifier];
|
||||
|
||||
if (existingVideo) {
|
||||
setVideoData(existingVideo);
|
||||
} else {
|
||||
getVideoData(vid?.name, vid?.identifier);
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
setSuperlikelist(comments);
|
||||
} catch (error) {
|
||||
|
||||
console.error(error);
|
||||
} finally {
|
||||
dispatch(setIsLoadingGlobal(false));
|
||||
|
||||
setLoadingSuperLikes(false);
|
||||
}
|
||||
}, [hashMapVideos]);
|
||||
|
||||
// React.useEffect(() => {
|
||||
// if (name && id) {
|
||||
// const existingVideo = hashMapVideos[id];
|
||||
|
||||
// if (existingVideo) {
|
||||
// setVideoData(existingVideo);
|
||||
// checkforPlaylist(name, id, existingVideo?.code);
|
||||
// } else {
|
||||
// getVideoData(name, id);
|
||||
// }
|
||||
// }
|
||||
// }, [id, name]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (name && id) {
|
||||
checkforPlaylist(name, id);
|
||||
|
||||
}
|
||||
}, [id, name]);
|
||||
|
||||
// const getAvatar = React.useCallback(async (author: string) => {
|
||||
// try {
|
||||
// let url = await qortalRequest({
|
||||
// action: 'GET_QDN_RESOURCE_URL',
|
||||
// name: author,
|
||||
// service: 'THUMBNAIL',
|
||||
// identifier: 'qortal_avatar'
|
||||
// })
|
||||
|
||||
// setAvatarUrl(url)
|
||||
// dispatch(setUserAvatarHash({
|
||||
// name: author,
|
||||
// url
|
||||
// }))
|
||||
// } catch (error) { }
|
||||
// }, [])
|
||||
|
||||
// React.useEffect(() => {
|
||||
// if (name && !avatarUrl) {
|
||||
// const existingAvatar = userAvatarHash[name]
|
||||
|
||||
// if (existingAvatar) {
|
||||
// setAvatarUrl(existingAvatar)
|
||||
// } else {
|
||||
// getAvatar(name)
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// }, [name, userAvatarHash])
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current) {
|
||||
const height = contentRef.current.offsetHeight;
|
||||
if (height > 100) { // Assuming 100px is your threshold
|
||||
setDescriptionHeight(100)
|
||||
}
|
||||
}
|
||||
}, [videoData]);
|
||||
|
||||
|
||||
const nextVideo = useMemo(()=> {
|
||||
|
||||
const currentVideoIndex = playlistData?.videos?.findIndex((item)=> item?.identifier === videoData?.id)
|
||||
if(currentVideoIndex !== -1){
|
||||
const nextVideoIndex = currentVideoIndex + 1
|
||||
const findVideo = playlistData?.videos[nextVideoIndex] || null
|
||||
if(findVideo){
|
||||
const id = findVideo?.identifier?.replace("_metadata", "");
|
||||
return {
|
||||
...findVideo,
|
||||
service: 'VIDEO',
|
||||
identifier: id,
|
||||
jsonId: findVideo?.identifier
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}, [playlistData, videoData])
|
||||
|
||||
const onEndVideo = useCallback(()=> {
|
||||
const currentVideoIndex = playlistData?.videos?.findIndex((item)=> item?.identifier === videoData?.id)
|
||||
if(currentVideoIndex !== -1){
|
||||
const nextVideoIndex = currentVideoIndex + 1
|
||||
const findVideo = playlistData?.videos[nextVideoIndex] || null
|
||||
if(findVideo){
|
||||
getVideoData(findVideo?.name, findVideo?.identifier)
|
||||
setDoAutoPlay(true)
|
||||
}
|
||||
}
|
||||
|
||||
}, [videoData, playlistData])
|
||||
if (!nameAddress || !videoData?.id) return;
|
||||
getComments(videoData?.id, nameAddress);
|
||||
}, [getComments, videoData?.id, nameAddress]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -337,182 +409,231 @@ export const PlaylistContent = () => {
|
||||
>
|
||||
{videoData && videoData?.videos?.length === 0 ? (
|
||||
<>
|
||||
<Box sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<Typography>This playlist is empty</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Typography>This playlist is empty</Typography>
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{videoReference && (
|
||||
<VideoPlayer
|
||||
name={videoReference?.name}
|
||||
service={videoReference?.service}
|
||||
identifier={videoReference?.identifier}
|
||||
user={name}
|
||||
jsonId={id}
|
||||
poster={videoCover || ""}
|
||||
nextVideo={nextVideo}
|
||||
onEnd={onEndVideo}
|
||||
autoPlay={doAutoPlay}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Spacer height="15px" />
|
||||
<Box sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end'
|
||||
}}>
|
||||
<FileAttachmentContainer>
|
||||
|
||||
<FileAttachmentFont>
|
||||
save to disk
|
||||
</FileAttachmentFont>
|
||||
<FileElement
|
||||
fileInfo={{...videoReference,
|
||||
filename: videoData?.filename || videoData?.title?.slice(0,20) + '.mp4',
|
||||
mimeType: videoData?.videoType || '"video/mp4',
|
||||
|
||||
}}
|
||||
title={videoData?.filename || videoData?.title?.slice(0,20)}
|
||||
customStyles={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</FileElement>
|
||||
</FileAttachmentContainer>
|
||||
</Box>
|
||||
<VideoTitle
|
||||
variant="h1"
|
||||
color="textPrimary"
|
||||
sx={{
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{videoData?.title}
|
||||
</VideoTitle>
|
||||
{videoData?.created && (
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontSize: "12px",
|
||||
}}
|
||||
color={theme.palette.text.primary}
|
||||
>
|
||||
{formatDate(videoData.created)}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<Spacer height="15px" />
|
||||
<Box
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => {
|
||||
navigate(`/channel/${name}`);
|
||||
}}
|
||||
>
|
||||
<StyledCardHeaderComment
|
||||
sx={{
|
||||
"& .MuiCardHeader-content": {
|
||||
overflow: "hidden",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Avatar
|
||||
src={`/arbitrary/THUMBNAIL/${name}/qortal_avatar`}
|
||||
alt={`${name}'s avatar`}
|
||||
<VideoPlayer
|
||||
name={videoReference?.name}
|
||||
service={videoReference?.service}
|
||||
identifier={videoReference?.identifier}
|
||||
user={name}
|
||||
jsonId={id}
|
||||
poster={videoCover || ""}
|
||||
nextVideo={nextVideo}
|
||||
onEnd={onEndVideo}
|
||||
autoPlay={doAutoPlay}
|
||||
/>
|
||||
</Box>
|
||||
<StyledCardColComment>
|
||||
<AuthorTextComment
|
||||
color={
|
||||
theme.palette.mode === "light"
|
||||
? theme.palette.text.secondary
|
||||
: "#d6e8ff"
|
||||
}
|
||||
>
|
||||
{name}
|
||||
</AuthorTextComment>
|
||||
</StyledCardColComment>
|
||||
</StyledCardHeaderComment>
|
||||
</Box>
|
||||
<Spacer height="15px" />
|
||||
<Box
|
||||
sx={{
|
||||
background: "#333333",
|
||||
borderRadius: "5px",
|
||||
padding: "5px",
|
||||
width: "100%",
|
||||
cursor: !descriptionHeight ? "default" : isExpandedDescription ? "default" : "pointer",
|
||||
position: "relative",
|
||||
}}
|
||||
className={!descriptionHeight ? "": isExpandedDescription ? "" : "hover-click"}
|
||||
>
|
||||
{descriptionHeight && !isExpandedDescription && (
|
||||
)}
|
||||
|
||||
<Spacer height="15px" />
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<FileAttachmentContainer>
|
||||
<FileAttachmentFont>save to disk</FileAttachmentFont>
|
||||
<FileElement
|
||||
fileInfo={{
|
||||
...videoReference,
|
||||
filename:
|
||||
videoData?.filename ||
|
||||
videoData?.title?.slice(0, 20) + ".mp4",
|
||||
mimeType: videoData?.videoType || '"video/mp4',
|
||||
}}
|
||||
title={videoData?.filename || videoData?.title?.slice(0, 20)}
|
||||
customStyles={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</FileElement>
|
||||
</FileAttachmentContainer>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
marginTop: "20px",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<VideoTitle
|
||||
variant="h1"
|
||||
color="textPrimary"
|
||||
sx={{
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{videoData?.title}
|
||||
</VideoTitle>
|
||||
{videoData && (
|
||||
<SuperLike
|
||||
numberOfSuperlikes={numberOfSuperlikes}
|
||||
totalAmount={calculateAmountSuperlike}
|
||||
name={videoData?.user}
|
||||
service={videoData?.service}
|
||||
identifier={videoData?.id}
|
||||
onSuccess={(val) => {
|
||||
setSuperlikelist((prev) => [val, ...prev]);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{videoData?.created && (
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontSize: "12px",
|
||||
}}
|
||||
color={theme.palette.text.primary}
|
||||
>
|
||||
{formatDate(videoData.created)}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<Spacer height="15px" />
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "0px",
|
||||
right: "0px",
|
||||
left: "0px",
|
||||
bottom: "0px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => {
|
||||
if (isExpandedDescription) return;
|
||||
setIsExpandedDescription(true);
|
||||
navigate(`/channel/${name}`);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
ref={contentRef}
|
||||
sx={{
|
||||
height: !descriptionHeight ? 'auto' : isExpandedDescription ? "auto" : "100px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{videoData?.htmlDescription ? (
|
||||
<DisplayHtml html={videoData?.htmlDescription} />
|
||||
) : (
|
||||
<VideoDescription variant="body1" color="textPrimary" sx={{
|
||||
cursor: 'default'
|
||||
}}>
|
||||
{videoData?.fullDescription}
|
||||
</VideoDescription>
|
||||
)}
|
||||
</Box>
|
||||
{descriptionHeight && (
|
||||
<Typography
|
||||
onClick={() => {
|
||||
setIsExpandedDescription((prev) => !prev);
|
||||
}}
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
fontSize: "16px",
|
||||
cursor: "pointer",
|
||||
paddingLeft: "15px",
|
||||
paddingTop: "15px",
|
||||
}}
|
||||
>
|
||||
{isExpandedDescription ? "Show less" : "...more"}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
</Box>
|
||||
>
|
||||
<StyledCardHeaderComment
|
||||
sx={{
|
||||
"& .MuiCardHeader-content": {
|
||||
overflow: "hidden",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Avatar
|
||||
src={`/arbitrary/THUMBNAIL/${name}/qortal_avatar`}
|
||||
alt={`${name}'s avatar`}
|
||||
/>
|
||||
</Box>
|
||||
<StyledCardColComment>
|
||||
<AuthorTextComment
|
||||
color={
|
||||
theme.palette.mode === "light"
|
||||
? theme.palette.text.secondary
|
||||
: "#d6e8ff"
|
||||
}
|
||||
>
|
||||
{name}
|
||||
</AuthorTextComment>
|
||||
</StyledCardColComment>
|
||||
</StyledCardHeaderComment>
|
||||
</Box>
|
||||
<Spacer height="15px" />
|
||||
<Box
|
||||
sx={{
|
||||
background: "#333333",
|
||||
borderRadius: "5px",
|
||||
padding: "5px",
|
||||
width: "100%",
|
||||
cursor: !descriptionHeight
|
||||
? "default"
|
||||
: isExpandedDescription
|
||||
? "default"
|
||||
: "pointer",
|
||||
position: "relative",
|
||||
}}
|
||||
className={
|
||||
!descriptionHeight
|
||||
? ""
|
||||
: isExpandedDescription
|
||||
? ""
|
||||
: "hover-click"
|
||||
}
|
||||
>
|
||||
{descriptionHeight && !isExpandedDescription && (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "0px",
|
||||
right: "0px",
|
||||
left: "0px",
|
||||
bottom: "0px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => {
|
||||
if (isExpandedDescription) return;
|
||||
setIsExpandedDescription(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
ref={contentRef}
|
||||
sx={{
|
||||
height: !descriptionHeight
|
||||
? "auto"
|
||||
: isExpandedDescription
|
||||
? "auto"
|
||||
: "100px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{videoData?.htmlDescription ? (
|
||||
<DisplayHtml html={videoData?.htmlDescription} />
|
||||
) : (
|
||||
<VideoDescription
|
||||
variant="body1"
|
||||
color="textPrimary"
|
||||
sx={{
|
||||
cursor: "default",
|
||||
}}
|
||||
>
|
||||
{videoData?.fullDescription}
|
||||
</VideoDescription>
|
||||
)}
|
||||
</Box>
|
||||
{descriptionHeight && (
|
||||
<Typography
|
||||
onClick={() => {
|
||||
setIsExpandedDescription((prev) => !prev);
|
||||
}}
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
fontSize: "16px",
|
||||
cursor: "pointer",
|
||||
paddingLeft: "15px",
|
||||
paddingTop: "15px",
|
||||
}}
|
||||
>
|
||||
{isExpandedDescription ? "Show less" : "...more"}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
</VideoPlayerContainer>
|
||||
|
||||
<SuperLikesSection
|
||||
getMore={() => {}}
|
||||
loadingSuperLikes={loadingSuperLikes}
|
||||
superlikes={superlikeList}
|
||||
postId={videoData?.id || ""}
|
||||
postName={videoData?.user || ""}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@ -521,9 +642,13 @@ export const PlaylistContent = () => {
|
||||
maxWidth: "1200px",
|
||||
}}
|
||||
>
|
||||
<CommentSection postId={id || ""} postName={name || ""} />
|
||||
<CommentSection postId={videoData?.id || ""} postName={name || ""} />
|
||||
{playlistData && (
|
||||
<Playlists playlistData={playlistData} currentVideoIdentifier={videoData?.id} onClick={getVideoData} />
|
||||
<Playlists
|
||||
playlistData={playlistData}
|
||||
currentVideoIdentifier={videoData?.id}
|
||||
onClick={getVideoData}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
@ -41,6 +41,7 @@ import { SuperLike } from "../../components/common/SuperLike/SuperLike";
|
||||
import { CommentContainer } from "../../components/common/Comments/Comments-styles";
|
||||
import { Comment } from "../../components/common/Comments/Comment";
|
||||
import { SuperLikesSection } from "../../components/common/SuperLikesList/SuperLikesSection";
|
||||
import { useFetchSuperLikes } from "../../hooks/useFetchSuperLikes";
|
||||
|
||||
export function isTimestampWithinRange(resTimestamp, resCreated) {
|
||||
// Calculate the absolute difference in milliseconds
|
||||
@ -106,7 +107,7 @@ export const VideoContent = () => {
|
||||
useState<boolean>(false);
|
||||
const [superlikeList, setSuperlikelist] = useState<any[]>([])
|
||||
const [loadingSuperLikes, setLoadingSuperLikes] = useState<boolean>(false)
|
||||
|
||||
const {addSuperlikeRawDataGetToList} = useFetchSuperLikes()
|
||||
|
||||
const calculateAmountSuperlike = useMemo(()=> {
|
||||
const totalQort = superlikeList?.reduce((acc, curr)=> {
|
||||
@ -249,36 +250,7 @@ export const VideoContent = () => {
|
||||
}
|
||||
}, [id, name]);
|
||||
|
||||
// const getAvatar = React.useCallback(async (author: string) => {
|
||||
// try {
|
||||
// let url = await qortalRequest({
|
||||
// action: 'GET_QDN_RESOURCE_URL',
|
||||
// name: author,
|
||||
// service: 'THUMBNAIL',
|
||||
// identifier: 'qortal_avatar'
|
||||
// })
|
||||
|
||||
// setAvatarUrl(url)
|
||||
// dispatch(setUserAvatarHash({
|
||||
// name: author,
|
||||
// url
|
||||
// }))
|
||||
// } catch (error) { }
|
||||
// }, [])
|
||||
|
||||
// React.useEffect(() => {
|
||||
// if (name && !avatarUrl) {
|
||||
// const existingAvatar = userAvatarHash[name]
|
||||
|
||||
// if (existingAvatar) {
|
||||
// setAvatarUrl(existingAvatar)
|
||||
// } else {
|
||||
// getAvatar(name)
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// }, [name, userAvatarHash])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current) {
|
||||
@ -313,23 +285,16 @@ export const VideoContent = () => {
|
||||
|
||||
|
||||
try {
|
||||
|
||||
const result = extractSigValue(comment?.metadata?.description)
|
||||
if(!result) continue
|
||||
const res = await getPaymentInfo(result);
|
||||
if(+res?.amount >= minPriceSuperlike && res.recipient === nameAddressParam && isTimestampWithinRange(res?.timestamp, comment.created)){
|
||||
addSuperlikeRawDataGetToList({name:comment.name, identifier:comment.identifier, content: comment})
|
||||
|
||||
const url = `/arbitrary/BLOG_COMMENT/${comment.name}/${comment.identifier}`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if(!response.ok) continue
|
||||
const responseData2 = await response.text();
|
||||
comments = [...comments, {
|
||||
...comment,
|
||||
message: responseData2,
|
||||
message: "",
|
||||
amount: res.amount
|
||||
}];
|
||||
|
||||
|
@ -6,6 +6,7 @@ interface GlobalState {
|
||||
videos: Video[]
|
||||
filteredVideos: Video[]
|
||||
hashMapVideos: Record<string, Video>
|
||||
hashMapSuperlikes: Record<string, any>
|
||||
countNewVideos: number
|
||||
isFiltering: boolean
|
||||
filterValue: string
|
||||
@ -21,6 +22,7 @@ const initialState: GlobalState = {
|
||||
videos: [],
|
||||
filteredVideos: [],
|
||||
hashMapVideos: {},
|
||||
hashMapSuperlikes: {},
|
||||
countNewVideos: 0,
|
||||
isFiltering: false,
|
||||
filterValue: '',
|
||||
@ -113,6 +115,10 @@ export const videoSlice = createSlice({
|
||||
const video = action.payload
|
||||
state.hashMapVideos[video.id] = video
|
||||
},
|
||||
addtoHashMapSuperlikes: (state, action) => {
|
||||
const superlike = action.payload
|
||||
state.hashMapSuperlikes[superlike.identifier] = superlike
|
||||
},
|
||||
updateInHashMap: (state, action) => {
|
||||
const { id } = action.payload
|
||||
const video = action.payload
|
||||
@ -197,7 +203,8 @@ export const {
|
||||
changeSelectedSubCategoryVideos,
|
||||
blockUser,
|
||||
setEditVideo,
|
||||
setEditPlaylist
|
||||
setEditPlaylist,
|
||||
addtoHashMapSuperlikes
|
||||
} = videoSlice.actions
|
||||
|
||||
export default videoSlice.reducer
|
||||
|
@ -27,6 +27,8 @@ interface Props {
|
||||
let timer: number | null = null;
|
||||
|
||||
export const queue = new RequestQueue();
|
||||
export const queueSuperlikes = new RequestQueue();
|
||||
|
||||
|
||||
const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
Loading…
x
Reference in New Issue
Block a user