mirror of
https://github.com/Qortal/q-tube.git
synced 2025-02-14 11:15:52 +00:00
Merge pull request #23 from QortalSeth/main
Follow, Like, and Dislike buttons Added to Video, Playlist, and Channel pages
This commit is contained in:
commit
b8463daef0
@ -8,14 +8,14 @@ import { Provider } from "react-redux";
|
||||
import GlobalWrapper from "./wrappers/GlobalWrapper";
|
||||
import Notification from "./components/common/Notification/Notification";
|
||||
import { Home } from "./pages/Home/Home";
|
||||
import { VideoContent } from "./pages/VideoContent/VideoContent";
|
||||
import { VideoContent } from "./pages/ContentPages/VideoContent/VideoContent";
|
||||
import DownloadWrapper from "./wrappers/DownloadWrapper";
|
||||
import { IndividualProfile } from "./pages/IndividualProfile/IndividualProfile";
|
||||
import { PlaylistContent } from "./pages/PlaylistContent/PlaylistContent";
|
||||
import { IndividualProfile } from "./pages/ContentPages/IndividualProfile/IndividualProfile";
|
||||
import { PlaylistContent } from "./pages/ContentPages/PlaylistContent/PlaylistContent";
|
||||
import { PersistGate } from "redux-persist/integration/react";
|
||||
import { persistStore } from "redux-persist";
|
||||
import { setFilteredSubscriptions } from "./state/features/videoSlice.ts";
|
||||
import { SubscriptionData } from "./components/common/SubscribeButton.tsx";
|
||||
import { SubscriptionData } from "./components/common/ContentButtons/SubscribeButton.tsx";
|
||||
|
||||
export const getUserName = async () => {
|
||||
const account = await qortalRequest({
|
||||
|
@ -3,7 +3,7 @@ import { CardContentContainerComment } from "../common/Comments/Comments-styles"
|
||||
import {
|
||||
CrowdfundSubTitle,
|
||||
CrowdfundSubTitleRow,
|
||||
} from "../PublishVideo/PublishVideo-styles.tsx";
|
||||
} from "../Publish/PublishVideo/PublishVideo-styles.tsx";
|
||||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
NewCrowdfundTitle,
|
||||
StyledButton,
|
||||
TimesIcon,
|
||||
} from "./Upload-styles";
|
||||
} from "./Upload-styles.tsx";
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
@ -30,9 +30,9 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import AddBoxIcon from "@mui/icons-material/AddBox";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
|
||||
import { setNotification } from "../../state/features/notificationsSlice";
|
||||
import { objectToBase64, uint8ArrayToBase64 } from "../../utils/toBase64";
|
||||
import { RootState } from "../../state/store";
|
||||
import { setNotification } from "../../../state/features/notificationsSlice.ts";
|
||||
import { objectToBase64, uint8ArrayToBase64 } from "../../../utils/toBase64.ts";
|
||||
import { RootState } from "../../../state/store.ts";
|
||||
import {
|
||||
upsertVideosBeginning,
|
||||
addToHashMap,
|
||||
@ -41,17 +41,17 @@ import {
|
||||
updateVideo,
|
||||
updateInHashMap,
|
||||
setEditPlaylist,
|
||||
} from "../../state/features/videoSlice";
|
||||
import ImageUploader from "../common/ImageUploader";
|
||||
import { categories, subCategories } from "../../constants/Categories.ts";
|
||||
import { Playlists } from "../Playlists/Playlists";
|
||||
import { PlaylistListEdit } from "../PlaylistListEdit/PlaylistListEdit";
|
||||
import { TextEditor } from "../common/TextEditor/TextEditor";
|
||||
import { extractTextFromHTML } from "../common/TextEditor/utils";
|
||||
} from "../../../state/features/videoSlice.ts";
|
||||
import ImageUploader from "../../common/ImageUploader.tsx";
|
||||
import { categories, subCategories } from "../../../constants/Categories.ts";
|
||||
import { Playlists } from "../../Playlists/Playlists.tsx";
|
||||
import { PlaylistListEdit } from "../PlaylistListEdit/PlaylistListEdit.tsx";
|
||||
import { TextEditor } from "../../common/TextEditor/TextEditor.tsx";
|
||||
import { extractTextFromHTML } from "../../common/TextEditor/utils.ts";
|
||||
import {
|
||||
QTUBE_PLAYLIST_BASE,
|
||||
QTUBE_VIDEO_BASE,
|
||||
} from "../../constants/Identifiers.ts";
|
||||
} from "../../../constants/Identifiers.ts";
|
||||
|
||||
const uid = new ShortUniqueId();
|
||||
const shortuid = new ShortUniqueId({ length: 5 });
|
@ -9,10 +9,10 @@ import {
|
||||
Rating,
|
||||
TextField,
|
||||
Typography,
|
||||
Select
|
||||
Select,
|
||||
} from "@mui/material";
|
||||
import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate";
|
||||
import { TimesSVG } from "../../assets/svgs/TimesSVG";
|
||||
import { TimesSVG } from "../../../assets/svgs/TimesSVG.tsx";
|
||||
|
||||
export const DoubleLine = styled(Typography)`
|
||||
display: -webkit-box;
|
||||
@ -159,8 +159,6 @@ export const CustomInputField = styled(TextField)(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
|
||||
export const CrowdfundTitle = styled(Typography)(({ theme }) => ({
|
||||
fontFamily: "Copse",
|
||||
letterSpacing: "1px",
|
||||
@ -539,8 +537,8 @@ export const NoReviewsFont = styled(Typography)(({ theme }) => ({
|
||||
|
||||
export const StyledButton = styled(Button)(({ theme }) => ({
|
||||
fontWeight: 600,
|
||||
color: theme.palette.text.primary
|
||||
}))
|
||||
color: theme.palette.text.primary,
|
||||
}));
|
||||
|
||||
export const CustomSelect = styled(Select)(({ theme }) => ({
|
||||
fontFamily: "Mulish",
|
||||
@ -549,34 +547,34 @@ export const CustomSelect = styled(Select)(({ theme }) => ({
|
||||
fontWeight: 400,
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
'& .MuiSelect-select': {
|
||||
padding: '12px',
|
||||
"& .MuiSelect-select": {
|
||||
padding: "12px",
|
||||
fontFamily: "Mulish",
|
||||
fontSize: "19px",
|
||||
letterSpacing: "0px",
|
||||
fontWeight: 400,
|
||||
borderRadius: theme.shape.borderRadius, // Match border radius
|
||||
},
|
||||
'&:before': {
|
||||
"&:before": {
|
||||
// Underline style
|
||||
borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf",
|
||||
},
|
||||
'&:after': {
|
||||
"&:after": {
|
||||
// Underline style when focused
|
||||
borderBottomColor: theme.palette.secondary.main,
|
||||
},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'& fieldset': {
|
||||
"& .MuiOutlinedInput-root": {
|
||||
"& fieldset": {
|
||||
borderColor: "#E0E3E7",
|
||||
},
|
||||
'&:hover fieldset': {
|
||||
"&:hover fieldset": {
|
||||
borderColor: "#B2BAC2",
|
||||
},
|
||||
'&.Mui-focused fieldset': {
|
||||
"&.Mui-focused fieldset": {
|
||||
borderColor: "#6F7E8C",
|
||||
},
|
||||
},
|
||||
'& .MuiInputBase-root': {
|
||||
"& .MuiInputBase-root": {
|
||||
fontFamily: "Mulish",
|
||||
fontSize: "19px",
|
||||
letterSpacing: "0px",
|
@ -9,10 +9,10 @@ import {
|
||||
Rating,
|
||||
TextField,
|
||||
Typography,
|
||||
Select
|
||||
Select,
|
||||
} from "@mui/material";
|
||||
import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate";
|
||||
import { TimesSVG } from "../../assets/svgs/TimesSVG";
|
||||
import { TimesSVG } from "../../../assets/svgs/TimesSVG.tsx";
|
||||
|
||||
export const DoubleLine = styled(Typography)`
|
||||
display: -webkit-box;
|
||||
@ -159,8 +159,6 @@ export const CustomInputField = styled(TextField)(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
|
||||
export const CrowdfundTitle = styled(Typography)(({ theme }) => ({
|
||||
fontFamily: "Copse",
|
||||
letterSpacing: "1px",
|
||||
@ -539,8 +537,8 @@ export const NoReviewsFont = styled(Typography)(({ theme }) => ({
|
||||
|
||||
export const StyledButton = styled(Button)(({ theme }) => ({
|
||||
fontWeight: 600,
|
||||
color: theme.palette.text.primary
|
||||
}))
|
||||
color: theme.palette.text.primary,
|
||||
}));
|
||||
|
||||
export const CustomSelect = styled(Select)(({ theme }) => ({
|
||||
fontFamily: "Mulish",
|
||||
@ -549,34 +547,34 @@ export const CustomSelect = styled(Select)(({ theme }) => ({
|
||||
fontWeight: 400,
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
'& .MuiSelect-select': {
|
||||
padding: '12px',
|
||||
"& .MuiSelect-select": {
|
||||
padding: "12px",
|
||||
fontFamily: "Mulish",
|
||||
fontSize: "19px",
|
||||
letterSpacing: "0px",
|
||||
fontWeight: 400,
|
||||
borderRadius: theme.shape.borderRadius, // Match border radius
|
||||
},
|
||||
'&:before': {
|
||||
"&:before": {
|
||||
// Underline style
|
||||
borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf",
|
||||
},
|
||||
'&:after': {
|
||||
"&:after": {
|
||||
// Underline style when focused
|
||||
borderBottomColor: theme.palette.secondary.main,
|
||||
},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'& fieldset': {
|
||||
"& .MuiOutlinedInput-root": {
|
||||
"& fieldset": {
|
||||
borderColor: "#E0E3E7",
|
||||
},
|
||||
'&:hover fieldset': {
|
||||
"&:hover fieldset": {
|
||||
borderColor: "#B2BAC2",
|
||||
},
|
||||
'&.Mui-focused fieldset': {
|
||||
"&.Mui-focused fieldset": {
|
||||
borderColor: "#6F7E8C",
|
||||
},
|
||||
},
|
||||
'& .MuiInputBase-root': {
|
||||
"& .MuiInputBase-root": {
|
||||
fontFamily: "Mulish",
|
||||
fontSize: "19px",
|
||||
letterSpacing: "0px",
|
@ -34,9 +34,9 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import AddBoxIcon from "@mui/icons-material/AddBox";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
|
||||
import { setNotification } from "../../state/features/notificationsSlice";
|
||||
import { objectToBase64, uint8ArrayToBase64 } from "../../utils/toBase64";
|
||||
import { RootState } from "../../state/store";
|
||||
import { setNotification } from "../../../state/features/notificationsSlice.ts";
|
||||
import { objectToBase64, uint8ArrayToBase64 } from "../../../utils/toBase64.ts";
|
||||
import { RootState } from "../../../state/store.ts";
|
||||
import {
|
||||
upsertVideosBeginning,
|
||||
addToHashMap,
|
||||
@ -44,16 +44,16 @@ import {
|
||||
setEditVideo,
|
||||
updateVideo,
|
||||
updateInHashMap,
|
||||
} from "../../state/features/videoSlice";
|
||||
import ImageUploader from "../common/ImageUploader";
|
||||
import { categories, subCategories } from "../../constants/Categories.ts";
|
||||
import { MultiplePublish } from "../common/MultiplePublish/MultiplePublishAll";
|
||||
import { TextEditor } from "../common/TextEditor/TextEditor";
|
||||
import { extractTextFromHTML } from "../common/TextEditor/utils";
|
||||
} from "../../../state/features/videoSlice.ts";
|
||||
import ImageUploader from "../../common/ImageUploader.tsx";
|
||||
import { categories, subCategories } from "../../../constants/Categories.ts";
|
||||
import { MultiplePublish } from "../MultiplePublish/MultiplePublishAll.tsx";
|
||||
import { TextEditor } from "../../common/TextEditor/TextEditor.tsx";
|
||||
import { extractTextFromHTML } from "../../common/TextEditor/utils.ts";
|
||||
import { toBase64 } from "../PublishVideo/PublishVideo.tsx";
|
||||
import { FrameExtractor } from "../common/FrameExtractor/FrameExtractor";
|
||||
import { QTUBE_VIDEO_BASE } from "../../constants/Identifiers.ts";
|
||||
import { titleFormatter } from "../../constants/Misc.ts";
|
||||
import { FrameExtractor } from "../../common/FrameExtractor/FrameExtractor.tsx";
|
||||
import { QTUBE_VIDEO_BASE } from "../../../constants/Identifiers.ts";
|
||||
import { titleFormatter } from "../../../constants/Misc.ts";
|
||||
|
||||
const uid = new ShortUniqueId();
|
||||
const shortuid = new ShortUniqueId({ length: 5 });
|
@ -8,8 +8,8 @@ import {
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import React, { useCallback, useEffect, useState, useRef } from "react";
|
||||
import { CircleSVG } from "../../../assets/svgs/CircleSVG";
|
||||
import { EmptyCircleSVG } from "../../../assets/svgs/EmptyCircleSVG";
|
||||
import { CircleSVG } from "../../../assets/svgs/CircleSVG.tsx";
|
||||
import { EmptyCircleSVG } from "../../../assets/svgs/EmptyCircleSVG.tsx";
|
||||
import { styled } from "@mui/system";
|
||||
|
||||
interface Publish {
|
@ -1,5 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import { CardContentContainerComment } from "../common/Comments/Comments-styles";
|
||||
import { CardContentContainerComment } from "../../common/Comments/Comments-styles.tsx";
|
||||
import {
|
||||
CrowdfundSubTitle,
|
||||
CrowdfundSubTitleRow,
|
||||
@ -7,11 +7,11 @@ import {
|
||||
import { Box, Button, Input, Typography, useTheme } from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
|
||||
import { removeVideo } from "../../state/features/videoSlice";
|
||||
import { removeVideo } from "../../../state/features/videoSlice.ts";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../state/store";
|
||||
import { QTUBE_VIDEO_BASE } from "../../constants/Identifiers.ts";
|
||||
import { RootState } from "../../../state/store.ts";
|
||||
import { QTUBE_VIDEO_BASE } from "../../../constants/Identifiers.ts";
|
||||
export const PlaylistListEdit = ({ playlistData, removeVideo, addVideo }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
@ -12,7 +12,7 @@ import {
|
||||
Select,
|
||||
} from "@mui/material";
|
||||
import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate";
|
||||
import { TimesSVG } from "../../assets/svgs/TimesSVG";
|
||||
import { TimesSVG } from "../../../assets/svgs/TimesSVG.tsx";
|
||||
|
||||
export const DoubleLine = styled(Typography)`
|
||||
display: -webkit-box;
|
@ -37,36 +37,36 @@ import AddBoxIcon from "@mui/icons-material/AddBox";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
|
||||
import { setNotification } from "../../state/features/notificationsSlice";
|
||||
import { objectToBase64, uint8ArrayToBase64 } from "../../utils/toBase64";
|
||||
import { RootState } from "../../state/store";
|
||||
import { setNotification } from "../../../state/features/notificationsSlice.ts";
|
||||
import { objectToBase64, uint8ArrayToBase64 } from "../../../utils/toBase64.ts";
|
||||
import { RootState } from "../../../state/store.ts";
|
||||
import {
|
||||
upsertVideosBeginning,
|
||||
addToHashMap,
|
||||
upsertVideos,
|
||||
} from "../../state/features/videoSlice";
|
||||
import ImageUploader from "../common/ImageUploader";
|
||||
import { categories, subCategories } from "../../constants/Categories.ts";
|
||||
import { MultiplePublish } from "../common/MultiplePublish/MultiplePublishAll";
|
||||
} from "../../../state/features/videoSlice.ts";
|
||||
import ImageUploader from "../../common/ImageUploader.tsx";
|
||||
import { categories, subCategories } from "../../../constants/Categories.ts";
|
||||
import { MultiplePublish } from "../MultiplePublish/MultiplePublishAll.tsx";
|
||||
import {
|
||||
CrowdfundSubTitle,
|
||||
CrowdfundSubTitleRow,
|
||||
} from "../EditPlaylist/Upload-styles";
|
||||
import { CardContentContainerComment } from "../common/Comments/Comments-styles";
|
||||
import { TextEditor } from "../common/TextEditor/TextEditor";
|
||||
import { extractTextFromHTML } from "../common/TextEditor/utils";
|
||||
} from "../EditPlaylist/Upload-styles.tsx";
|
||||
import { CardContentContainerComment } from "../../common/Comments/Comments-styles.tsx";
|
||||
import { TextEditor } from "../../common/TextEditor/TextEditor.tsx";
|
||||
import { extractTextFromHTML } from "../../common/TextEditor/utils.ts";
|
||||
import {
|
||||
FiltersCheckbox,
|
||||
FiltersRow,
|
||||
FiltersSubContainer,
|
||||
} from "../../pages/Home/VideoList-styles";
|
||||
import { FrameExtractor } from "../common/FrameExtractor/FrameExtractor";
|
||||
} from "../../../pages/Home/VideoList-styles.tsx";
|
||||
import { FrameExtractor } from "../../common/FrameExtractor/FrameExtractor.tsx";
|
||||
import {
|
||||
QTUBE_PLAYLIST_BASE,
|
||||
QTUBE_VIDEO_BASE,
|
||||
} from "../../constants/Identifiers.ts";
|
||||
import { titleFormatter } from "../../constants/Misc.ts";
|
||||
import { getFileName } from "../../utils/stringFunctions.ts";
|
||||
} from "../../../constants/Identifiers.ts";
|
||||
import { titleFormatter } from "../../../constants/Misc.ts";
|
||||
import { getFileName } from "../../../utils/stringFunctions.ts";
|
||||
|
||||
export const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
|
||||
new Promise((resolve, reject) => {
|
@ -12,8 +12,6 @@ export const StatsData = () => {
|
||||
width: "100%",
|
||||
padding: "20px 0px",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
borderTop: `1px solid ${theme.palette.background.paper}`,
|
||||
borderRight: `1px solid ${theme.palette.background.paper}`,
|
||||
}));
|
||||
|
||||
const {
|
||||
@ -51,7 +49,10 @@ export const StatsData = () => {
|
||||
</div>
|
||||
<div>
|
||||
Average:{" "}
|
||||
<span style={{ fontWeight: "bold" }}>{videosPerNamePublished}</span>
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
{videosPerNamePublished > 0 &&
|
||||
Number(videosPerNamePublished).toFixed(0)}
|
||||
</span>
|
||||
</div>
|
||||
</StatsCol>
|
||||
);
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
import {
|
||||
CrowdfundSubTitle,
|
||||
CrowdfundSubTitleRow,
|
||||
} from "../../PublishVideo/PublishVideo-styles.tsx";
|
||||
} from "../../Publish/PublishVideo/PublishVideo-styles.tsx";
|
||||
import { COMMENT_BASE } from "../../../constants/Identifiers.ts";
|
||||
|
||||
interface CommentSectionProps {
|
||||
|
159
src/components/common/ContentButtons/FollowButton.tsx
Normal file
159
src/components/common/ContentButtons/FollowButton.tsx
Normal file
@ -0,0 +1,159 @@
|
||||
import { Box, Button, ButtonProps } from "@mui/material";
|
||||
import Tooltip, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip";
|
||||
import { MouseEvent, useEffect, useState } from "react";
|
||||
import { styled } from "@mui/material/styles";
|
||||
|
||||
interface FollowButtonProps extends ButtonProps {
|
||||
followerName: string;
|
||||
}
|
||||
|
||||
export type FollowData = {
|
||||
userName: string;
|
||||
followerName: string;
|
||||
};
|
||||
|
||||
export const FollowButton = ({ followerName, ...props }: FollowButtonProps) => {
|
||||
const [followingList, setFollowingList] = useState<string[]>([]);
|
||||
const [followingSize, setFollowingSize] = useState<string>("");
|
||||
const [followingItemCount, setFollowingItemCount] = useState<string>("");
|
||||
const isFollowingName = () => {
|
||||
return followingList.includes(followerName);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
qortalRequest({
|
||||
action: "GET_LIST_ITEMS",
|
||||
list_name: "followedNames",
|
||||
}).then(followList => {
|
||||
setFollowingList(followList);
|
||||
});
|
||||
getFollowSize();
|
||||
}, []);
|
||||
|
||||
const followName = () => {
|
||||
if (followingList.includes(followerName) === false) {
|
||||
qortalRequest({
|
||||
action: "ADD_LIST_ITEM",
|
||||
list_name: "followedNames",
|
||||
item: followerName,
|
||||
}).then(response => {
|
||||
if (response === false) console.log("followName failed");
|
||||
else {
|
||||
setFollowingList([...followingList, followerName]);
|
||||
console.log("following Name: ", followerName);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
const unfollowName = () => {
|
||||
if (followingList.includes(followerName)) {
|
||||
qortalRequest({
|
||||
action: "DELETE_LIST_ITEM",
|
||||
list_name: "followedNames",
|
||||
item: followerName,
|
||||
}).then(response => {
|
||||
if (response === false) console.log("unfollowName failed");
|
||||
else {
|
||||
const listWithoutName = followingList.filter(
|
||||
item => followerName !== item
|
||||
);
|
||||
setFollowingList(listWithoutName);
|
||||
console.log("unfollowing Name: ", followerName);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const manageFollow = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
isFollowingName() ? unfollowName() : followName();
|
||||
};
|
||||
|
||||
const verticalPadding = "3px";
|
||||
const horizontalPadding = "8px";
|
||||
const buttonStyle = {
|
||||
fontSize: "15px",
|
||||
fontWeight: "700",
|
||||
paddingTop: verticalPadding,
|
||||
paddingBottom: verticalPadding,
|
||||
paddingLeft: horizontalPadding,
|
||||
paddingRight: horizontalPadding,
|
||||
borderRadius: 28,
|
||||
color: "white",
|
||||
width: "96px",
|
||||
height: "45px",
|
||||
...props.sx,
|
||||
};
|
||||
|
||||
const getFollowSize = () => {
|
||||
qortalRequest({
|
||||
action: "LIST_QDN_RESOURCES",
|
||||
name: followerName,
|
||||
limit: 0,
|
||||
includeMetadata: false,
|
||||
}).then(publishesList => {
|
||||
let totalSize = 0;
|
||||
let itemsCount = 0;
|
||||
publishesList.map(publish => {
|
||||
totalSize += +publish.size;
|
||||
itemsCount++;
|
||||
});
|
||||
setFollowingSize(formatBytes(totalSize));
|
||||
setFollowingItemCount(itemsCount.toString());
|
||||
});
|
||||
};
|
||||
|
||||
function formatBytes(bytes: number, decimals = 2) {
|
||||
if (!+bytes) return "0 Bytes";
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
||||
}
|
||||
|
||||
const TooltipLine = styled("div")(({ theme }) => ({
|
||||
fontSize: "18px",
|
||||
}));
|
||||
|
||||
const tooltipTitle = followingSize && (
|
||||
<>
|
||||
<TooltipLine>
|
||||
Following a name automatically downloads all of its content to your
|
||||
node. The more followers a name has, the faster its content will
|
||||
download for everyone.
|
||||
</TooltipLine>
|
||||
<br />
|
||||
<TooltipLine>{`${followerName}'s Current Download Size: ${followingSize}`}</TooltipLine>
|
||||
<TooltipLine>{`Number of Files: ${followingItemCount}`}</TooltipLine>
|
||||
</>
|
||||
);
|
||||
|
||||
const CustomWidthTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||
<Tooltip {...props} classes={{ popper: className }} />
|
||||
))({
|
||||
[`& .${tooltipClasses.tooltip}`]: {
|
||||
maxWidth: 600,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomWidthTooltip title={tooltipTitle} placement={"top"} arrow>
|
||||
<Button
|
||||
{...props}
|
||||
variant={"contained"}
|
||||
color="success"
|
||||
sx={buttonStyle}
|
||||
onClick={e => manageFollow(e)}
|
||||
>
|
||||
{isFollowingName() ? "Unfollow" : "Follow"}
|
||||
</Button>
|
||||
</CustomWidthTooltip>
|
||||
</>
|
||||
);
|
||||
};
|
@ -0,0 +1,66 @@
|
||||
import { fetchResourcesByIdentifier } from "../../../utils/qortalRequestFunctions.ts";
|
||||
import { DISLIKE, LIKE, LikeType, NEUTRAL } from "./LikeAndDislike.tsx";
|
||||
|
||||
export const getCurrentLikeType = async (
|
||||
username: string,
|
||||
likeIdentifier: string
|
||||
): Promise<LikeType> => {
|
||||
try {
|
||||
const response = await qortalRequest({
|
||||
action: "FETCH_QDN_RESOURCE",
|
||||
name: username,
|
||||
service: "CHAIN_COMMENT",
|
||||
identifier: likeIdentifier,
|
||||
});
|
||||
return response?.likeType;
|
||||
} catch (e) {
|
||||
console.log("liketype error: ", e);
|
||||
return NEUTRAL;
|
||||
}
|
||||
};
|
||||
|
||||
type ResourceType = { likeType: LikeType };
|
||||
export type LikesAndDislikes = { likes: number; dislikes: number };
|
||||
const countLikesAndDislikes = (likesAndDislikes: ResourceType[]) => {
|
||||
let totalLikeCount = 0;
|
||||
let totalDislikeCount = 0;
|
||||
likesAndDislikes.map(likeOrDislike => {
|
||||
const likeType = likeOrDislike.likeType;
|
||||
if (likeType === LIKE) totalLikeCount += 1;
|
||||
if (likeType === DISLIKE) totalDislikeCount += 1;
|
||||
});
|
||||
return {
|
||||
likes: totalLikeCount,
|
||||
dislikes: totalDislikeCount,
|
||||
} as LikesAndDislikes;
|
||||
};
|
||||
export const getCurrentLikesAndDislikesCount = async (
|
||||
likeIdentifier: string
|
||||
) => {
|
||||
try {
|
||||
const likesAndDislikes = await fetchResourcesByIdentifier<ResourceType>(
|
||||
"CHAIN_COMMENT",
|
||||
likeIdentifier
|
||||
);
|
||||
return countLikesAndDislikes(likesAndDislikes);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export function formatLikeCount(likeCount: number, decimals = 2) {
|
||||
if (!+likeCount) return "";
|
||||
|
||||
const sigDigits = Math.floor(Math.log10(likeCount) / 3);
|
||||
if (sigDigits < 1) return likeCount.toString();
|
||||
|
||||
const sigDigitSize = 1000;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ["K", "M", "B"];
|
||||
|
||||
const sigDigitsToTheThousands = Math.pow(sigDigitSize, sigDigits);
|
||||
const sigDigitLikeCount = (likeCount / sigDigitsToTheThousands).toFixed(dm);
|
||||
|
||||
return `${sigDigitLikeCount}${sizes[sigDigits - 1] || ""}`;
|
||||
}
|
230
src/components/common/ContentButtons/LikeAndDislike.tsx
Normal file
230
src/components/common/ContentButtons/LikeAndDislike.tsx
Normal file
@ -0,0 +1,230 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import ThumbUpIcon from "@mui/icons-material/ThumbUp";
|
||||
import ThumbDownIcon from "@mui/icons-material/ThumbDown";
|
||||
import ThumbUpOffAltOutlinedIcon from "@mui/icons-material/ThumbUpOffAltOutlined";
|
||||
import ThumbDownOffAltOutlinedIcon from "@mui/icons-material/ThumbDownOffAltOutlined";
|
||||
import { Box, Tooltip } from "@mui/material";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { setNotification } from "../../../state/features/notificationsSlice.ts";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import { objectToBase64 } from "../../../utils/toBase64.ts";
|
||||
import { RootState } from "../../../state/store.ts";
|
||||
import { FOR, FOR_LIKE, LIKE_BASE } from "../../../constants/Identifiers.ts";
|
||||
import {
|
||||
formatLikeCount,
|
||||
getCurrentLikesAndDislikesCount,
|
||||
getCurrentLikeType,
|
||||
LikesAndDislikes,
|
||||
} from "./LikeAndDislike-functions.ts";
|
||||
|
||||
interface LikeAndDislikeProps {
|
||||
name: string;
|
||||
identifier: string;
|
||||
}
|
||||
export enum LikeType {
|
||||
Like = 1,
|
||||
Neutral = 0,
|
||||
Dislike = -1,
|
||||
}
|
||||
export const LIKE = LikeType.Like;
|
||||
export const DISLIKE = LikeType.Dislike;
|
||||
export const NEUTRAL = LikeType.Neutral;
|
||||
export const LikeAndDislike = ({ name, identifier }: LikeAndDislikeProps) => {
|
||||
const username = useSelector((state: RootState) => state.auth?.user?.name);
|
||||
const dispatch = useDispatch();
|
||||
const [likeCount, setLikeCount] = useState<number>(0);
|
||||
const [dislikeCount, setDislikeCount] = useState<number>(0);
|
||||
const [currentLikeType, setCurrentLikeType] = useState<LikeType>(NEUTRAL);
|
||||
const likeIdentifier = `${LIKE_BASE}${identifier.slice(0, 39)}`;
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
type PromiseReturn = [LikeType, LikesAndDislikes];
|
||||
|
||||
Promise.all([
|
||||
getCurrentLikeType(username, likeIdentifier),
|
||||
getCurrentLikesAndDislikesCount(likeIdentifier),
|
||||
]).then(([likeType, likesAndDislikes]: PromiseReturn) => {
|
||||
setCurrentLikeType(likeType);
|
||||
|
||||
setLikeCount(likesAndDislikes?.likes || 0);
|
||||
setDislikeCount(likesAndDislikes?.dislikes || 0);
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const updateLikeDataState = (newLikeType: LikeType) => {
|
||||
const setSuccessNotification = (msg: string) =>
|
||||
dispatch(
|
||||
setNotification({
|
||||
msg,
|
||||
alertType: "success",
|
||||
})
|
||||
);
|
||||
setCurrentLikeType(newLikeType);
|
||||
switch (newLikeType) {
|
||||
case NEUTRAL:
|
||||
if (currentLikeType === LIKE) {
|
||||
setLikeCount(count => count - 1);
|
||||
setSuccessNotification("Like Removed");
|
||||
} else {
|
||||
setDislikeCount(count => count - 1);
|
||||
setSuccessNotification("Dislike Removed");
|
||||
}
|
||||
|
||||
break;
|
||||
case LIKE:
|
||||
if (currentLikeType === DISLIKE) setDislikeCount(count => count - 1);
|
||||
setLikeCount(count => count + 1);
|
||||
setSuccessNotification("Like Successful");
|
||||
break;
|
||||
case DISLIKE:
|
||||
if (currentLikeType === LIKE) setLikeCount(count => count - 1);
|
||||
setDislikeCount(count => count + 1);
|
||||
setSuccessNotification("Dislike Successful");
|
||||
break;
|
||||
}
|
||||
};
|
||||
function publishLike(chosenLikeType: LikeType) {
|
||||
if (isLoading) {
|
||||
dispatch(
|
||||
setNotification({
|
||||
msg: "Wait for Like Data to load first",
|
||||
alertType: "error",
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
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 (!identifier) throw new Error("Could not retrieve id of video post");
|
||||
|
||||
if (username === name) {
|
||||
dispatch(
|
||||
setNotification({
|
||||
msg: "You cannot send yourself a like",
|
||||
alertType: "error",
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
qortalRequest({
|
||||
action: "GET_NAME_DATA",
|
||||
name: name,
|
||||
}).then(resName => {
|
||||
const address = resName.owner;
|
||||
if (!address)
|
||||
throw new Error("Could not retrieve content creator's address");
|
||||
});
|
||||
|
||||
objectToBase64({
|
||||
likeType: chosenLikeType,
|
||||
}).then(likeToBase64 => {
|
||||
qortalRequest({
|
||||
action: "PUBLISH_QDN_RESOURCE",
|
||||
name: username,
|
||||
service: "CHAIN_COMMENT",
|
||||
data64: likeToBase64,
|
||||
title: "",
|
||||
identifier: likeIdentifier,
|
||||
filename: `like_metadata.json`,
|
||||
}).then(() => {
|
||||
updateLikeDataState(chosenLikeType);
|
||||
});
|
||||
});
|
||||
} catch (error: any) {
|
||||
dispatch(
|
||||
setNotification({
|
||||
msg:
|
||||
error ||
|
||||
error?.error ||
|
||||
error?.message ||
|
||||
"Failed to publish Like or Dislike",
|
||||
alertType: "error",
|
||||
})
|
||||
);
|
||||
throw new Error("Failed to publish Super Like");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "15px",
|
||||
cursor: "pointer",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<Tooltip title="Like or Dislike Video" placement="top">
|
||||
<Box
|
||||
sx={{
|
||||
padding: "5px",
|
||||
borderRadius: "7px",
|
||||
gap: "5px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginRight: "10px",
|
||||
height: "53px",
|
||||
}}
|
||||
>
|
||||
{currentLikeType === LIKE ? (
|
||||
<ThumbUpIcon onClick={() => publishLike(NEUTRAL)} />
|
||||
) : (
|
||||
<ThumbUpOffAltOutlinedIcon onClick={() => publishLike(LIKE)} />
|
||||
)}
|
||||
{likeCount > 0 && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
<span style={{ marginRight: "10px", paddingBottom: "4px" }}>
|
||||
{formatLikeCount(likeCount)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentLikeType === DISLIKE ? (
|
||||
<ThumbDownIcon
|
||||
onClick={() => publishLike(NEUTRAL)}
|
||||
color={"error"}
|
||||
/>
|
||||
) : (
|
||||
<ThumbDownOffAltOutlinedIcon
|
||||
onClick={() => publishLike(DISLIKE)}
|
||||
color={"error"}
|
||||
/>
|
||||
)}
|
||||
{dislikeCount > 0 && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
marginRight: "10px",
|
||||
paddingBottom: "4px",
|
||||
color: "red",
|
||||
}}
|
||||
>
|
||||
{formatLikeCount(dislikeCount)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,10 +1,14 @@
|
||||
import { Button, ButtonProps } from "@mui/material";
|
||||
import { Button, ButtonProps, Tooltip } from "@mui/material";
|
||||
import { MouseEvent, useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { RootState } from "../../state/store.ts";
|
||||
import { subscribe, unSubscribe } from "../../state/features/persistSlice.ts";
|
||||
import { setFilteredSubscriptions } from "../../state/features/videoSlice.ts";
|
||||
import { subscriptionListFilter } from "../../App.tsx";
|
||||
import { RootState } from "../../../state/store.ts";
|
||||
import {
|
||||
subscribe,
|
||||
unSubscribe,
|
||||
} from "../../../state/features/persistSlice.ts";
|
||||
import { setFilteredSubscriptions } from "../../../state/features/videoSlice.ts";
|
||||
import { subscriptionListFilter } from "../../../App.tsx";
|
||||
import { styled } from "@mui/material/styles";
|
||||
|
||||
interface SubscribeButtonProps extends ButtonProps {
|
||||
subscriberName: string;
|
||||
@ -89,15 +93,31 @@ export const SubscribeButton = ({
|
||||
height: "45px",
|
||||
...props.sx,
|
||||
};
|
||||
|
||||
const TooltipLine = styled("div")(({ theme }) => ({
|
||||
fontSize: "18px",
|
||||
}));
|
||||
|
||||
const tooltipTitle = (
|
||||
<>
|
||||
<TooltipLine>
|
||||
Subscribing to a name lets you see their content on the Subscriptions
|
||||
tab of the Home Page. This does NOT download any data to your node.
|
||||
</TooltipLine>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
variant={"contained"}
|
||||
color="error"
|
||||
sx={buttonStyle}
|
||||
onClick={e => manageSubscription(e)}
|
||||
>
|
||||
{isSubscribed ? "Unsubscribe" : "Subscribe"}
|
||||
</Button>
|
||||
<Tooltip title={tooltipTitle} placement={"top"} arrow>
|
||||
<Button
|
||||
{...props}
|
||||
variant={"contained"}
|
||||
color="error"
|
||||
sx={buttonStyle}
|
||||
onClick={e => manageSubscription(e)}
|
||||
>
|
||||
{isSubscribed ? "Unsubscribe" : "Subscribe"}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
@ -17,26 +17,25 @@ import {
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import qortImg from "../../../assets/img/qort.png";
|
||||
import { MultiplePublish } from "../MultiplePublish/MultiplePublishAll";
|
||||
import { MultiplePublish } from "../../Publish/MultiplePublish/MultiplePublishAll.tsx";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { setNotification } from "../../../state/features/notificationsSlice";
|
||||
import { setNotification } from "../../../state/features/notificationsSlice.ts";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import { objectToBase64 } from "../../../utils/toBase64";
|
||||
import { objectToBase64 } from "../../../utils/toBase64.ts";
|
||||
import { minPriceSuperlike } from "../../../constants/Misc.ts";
|
||||
import { CommentInput } from "../Comments/Comments-styles";
|
||||
import { CommentInput } from "../Comments/Comments-styles.tsx";
|
||||
import {
|
||||
CrowdfundActionButton,
|
||||
CrowdfundActionButtonRow,
|
||||
ModalBody,
|
||||
NewCrowdfundTitle,
|
||||
Spacer,
|
||||
} from "../../PublishVideo/PublishVideo-styles.tsx";
|
||||
import { utf8ToBase64 } from "../SuperLikesList/CommentEditor";
|
||||
import { RootState } from "../../../state/store";
|
||||
} from "../../Publish/PublishVideo/PublishVideo-styles.tsx";
|
||||
import { utf8ToBase64 } from "../SuperLikesList/CommentEditor.tsx";
|
||||
import { RootState } from "../../../state/store.ts";
|
||||
import {
|
||||
FOR,
|
||||
FOR_SUPER_LIKE,
|
||||
QTUBE_VIDEO_BASE,
|
||||
SUPER_LIKE_BASE,
|
||||
} from "../../../constants/Identifiers.ts";
|
||||
import BoundedNumericTextField from "../../../utils/BoundedNumericTextField.tsx";
|
||||
@ -158,7 +157,7 @@ export const SuperLike = ({
|
||||
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 superLikeAmount for a Super like. Each Super like is verified before displaying to make there aren't any non-paid Super likes",
|
||||
"Super likes are a way to support your favorite content creators. Attach a message to the Super like and have your message seen before normal comments. There is a minimum superLikeAmount 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);
|
||||
@ -185,26 +184,16 @@ export const SuperLike = ({
|
||||
|
||||
setIsOpenMultiplePublish(true);
|
||||
} catch (error: any) {
|
||||
let notificationObj: any = null;
|
||||
if (typeof error === "string") {
|
||||
notificationObj = {
|
||||
msg: error || "Failed to publish Super Like",
|
||||
dispatch(
|
||||
setNotification({
|
||||
msg:
|
||||
error ||
|
||||
error?.error ||
|
||||
error?.message ||
|
||||
"Failed to publish Super Like",
|
||||
alertType: "error",
|
||||
};
|
||||
} else if (typeof error?.error === "string") {
|
||||
notificationObj = {
|
||||
msg: error?.error || "Failed to publish Super Like",
|
||||
alertType: "error",
|
||||
};
|
||||
} else {
|
||||
notificationObj = {
|
||||
msg: error?.message || "Failed to publish Super Like",
|
||||
alertType: "error",
|
||||
};
|
||||
}
|
||||
if (!notificationObj) return;
|
||||
dispatch(setNotification(notificationObj));
|
||||
|
||||
})
|
||||
);
|
||||
throw new Error("Failed to publish Super Like");
|
||||
}
|
||||
}
|
||||
@ -239,8 +228,6 @@ export const SuperLike = ({
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
|
||||
|
||||
<Tooltip title="Super Like" placement="top">
|
||||
<Box
|
||||
sx={{
|
||||
@ -250,8 +237,8 @@ export const SuperLike = ({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
outline: "1px gold solid",
|
||||
marginRight:'10px',
|
||||
height: '53px',
|
||||
marginRight: "10px",
|
||||
height: "53px",
|
||||
}}
|
||||
>
|
||||
<ThumbUpIcon
|
||||
@ -260,24 +247,30 @@ export const SuperLike = ({
|
||||
}}
|
||||
/>
|
||||
|
||||
{numberOfSuperlikes === 0 ? null : (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center', userSelect: "none"}}>
|
||||
<span style={{marginRight:'10px', paddingBottom:'4px'}}>{numberOfSuperlikes}</span>
|
||||
<img
|
||||
style={{
|
||||
height: "25px",
|
||||
width: "25px",
|
||||
marginRight:'5px',
|
||||
}}
|
||||
src={qortImg}
|
||||
alt={"Qort Icon"}
|
||||
/>
|
||||
{truncateNumber(totalAmount,0)}
|
||||
</div>
|
||||
)}
|
||||
{numberOfSuperlikes === 0 ? null : (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
<span style={{ marginRight: "10px", paddingBottom: "4px" }}>
|
||||
{numberOfSuperlikes}
|
||||
</span>
|
||||
<img
|
||||
style={{
|
||||
height: "25px",
|
||||
width: "25px",
|
||||
marginRight: "5px",
|
||||
}}
|
||||
src={qortImg}
|
||||
alt={"Qort Icon"}
|
||||
/>
|
||||
{truncateNumber(totalAmount, 0)}
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
@ -301,10 +294,10 @@ export const SuperLike = ({
|
||||
<DialogContent>
|
||||
<Box>
|
||||
<InputLabel htmlFor="standard-adornment-amount">
|
||||
Amount in QORT (min 10 QORT)
|
||||
Amount in QORT (min 1 QORT)
|
||||
</InputLabel>
|
||||
<BoundedNumericTextField
|
||||
minValue={10}
|
||||
minValue={+minPriceSuperlike}
|
||||
initialValue={minPriceSuperlike.toString()}
|
||||
maxValue={numberToInt(+currentBalance)}
|
||||
allowDecimals={false}
|
@ -24,7 +24,7 @@ import {
|
||||
extractSigValue,
|
||||
getPaymentInfo,
|
||||
isTimestampWithinRange,
|
||||
} from "../../../pages/VideoContent/VideoContent";
|
||||
} from "../../../pages/ContentPages/VideoContent/VideoContent";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import localForage from "localforage";
|
||||
import moment from "moment";
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
import {
|
||||
CrowdfundSubTitle,
|
||||
CrowdfundSubTitleRow,
|
||||
} from "../../PublishVideo/PublishVideo-styles.tsx";
|
||||
} from "../../Publish/PublishVideo/PublishVideo-styles.tsx";
|
||||
import { COMMENT_BASE } from "../../../constants/Identifiers.ts";
|
||||
|
||||
interface CommentSectionProps {
|
||||
|
@ -1,18 +1,19 @@
|
||||
import { styled } from "@mui/system";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
export const VideoContainer = styled(Box)`
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-height: 70vh;
|
||||
`;
|
||||
export const VideoContainer = styled(Box)(({ theme }) => ({
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
maxHeight: "70vh",
|
||||
"&:focus": { outline: "none" },
|
||||
}));
|
||||
|
||||
export const VideoElement = styled("video")`
|
||||
width: 100%;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -35,8 +35,8 @@ import {
|
||||
} from "../../../state/features/videoSlice";
|
||||
import { RootState } from "../../../state/store";
|
||||
import { useWindowSize } from "../../../hooks/useWindowSize";
|
||||
import { PublishVideo } from "../../PublishVideo/PublishVideo.tsx";
|
||||
import { StyledButton } from "../../PublishVideo/PublishVideo-styles.tsx";
|
||||
import { PublishVideo } from "../../Publish/PublishVideo/PublishVideo.tsx";
|
||||
import { StyledButton } from "../../Publish/PublishVideo/PublishVideo-styles.tsx";
|
||||
import { Notifications } from "../../common/Notifications/Notifications";
|
||||
interface Props {
|
||||
isAuthenticated: boolean;
|
||||
|
@ -39,6 +39,7 @@ export const categories = [
|
||||
{ id: 24, name: "Anime" },
|
||||
{ id: 25, name: "Cartoons" },
|
||||
{ id: 26, name: "Qortal" },
|
||||
{ id: 99, name: "Other" },
|
||||
].sort(sortCategory);
|
||||
|
||||
export const subCategories: CategoryMap = {
|
||||
@ -59,7 +60,7 @@ export const subCategories: CategoryMap = {
|
||||
{ id: 113, name: "Indie Films" },
|
||||
{ id: 114, name: "International Films" },
|
||||
{ id: 115, name: "Biographies & True Stories" },
|
||||
{ id: 116, name: "Other" },
|
||||
{ id: 199, name: "Other" },
|
||||
].sort(sortCategory),
|
||||
2: [
|
||||
// Series
|
||||
@ -78,14 +79,14 @@ export const subCategories: CategoryMap = {
|
||||
{ id: 213, name: "Anthologies" },
|
||||
{ id: 214, name: "International Series" },
|
||||
{ id: 215, name: "Miniseries" },
|
||||
{ id: 216, name: "Other" },
|
||||
{ id: 299, name: "Other" },
|
||||
].sort(sortCategory),
|
||||
4: [
|
||||
// Education
|
||||
{ id: 400, name: "Tutorial" },
|
||||
{ id: 401, name: "Documentary" },
|
||||
{ id: 401, name: "Qortal" },
|
||||
{ id: 402, name: "Other" },
|
||||
{ id: 402, name: "Documentary" },
|
||||
{ id: 499, name: "Other" },
|
||||
].sort(sortCategory),
|
||||
|
||||
24: [
|
||||
@ -102,6 +103,6 @@ export const subCategories: CategoryMap = {
|
||||
{ id: 2411, name: "Harem" },
|
||||
{ id: 2412, name: "Ecchi" },
|
||||
{ id: 2413, name: "Idol" },
|
||||
{ id: 2414, name: "Other" },
|
||||
{ id: 2499, name: "Other" },
|
||||
].sort(sortCategory),
|
||||
};
|
||||
|
@ -8,8 +8,12 @@ export const QTUBE_PLAYLIST_BASE = useTestIdentifiers
|
||||
export const SUPER_LIKE_BASE = useTestIdentifiers
|
||||
? "MYTEST_superlike_"
|
||||
: "qtube_superlike_";
|
||||
|
||||
export const LIKE_BASE = useTestIdentifiers ? "MYTEST_like_" : "qtube_like_";
|
||||
|
||||
export const COMMENT_BASE = useTestIdentifiers
|
||||
? "qcomment_v1_MYTEST_"
|
||||
: "qcomment_v1_qtube_";
|
||||
export const FOR = useTestIdentifiers ? "FORTEST5" : "FOR0962";
|
||||
export const FOR_SUPER_LIKE = useTestIdentifiers ? "MYTEST_sl" : `qtube_sl`;
|
||||
export const FOR_LIKE = useTestIdentifiers ? "MYTEST_like" : `qtube_like`;
|
||||
|
@ -1,6 +1,3 @@
|
||||
export const minPriceSuperlike = 10;
|
||||
export const minPriceSuperlike = 1;
|
||||
export const titleFormatter = /[^a-zA-Z0-9\s-_!?()&'",.;:|—~@#$%^*+=<>]/g;
|
||||
export const titleFormatterOnSave = /[^a-zA-Z0-9\s-_!()&',.;—~@#$%^+=]/g;
|
||||
|
||||
export const allTabValue = "all";
|
||||
export const subscriptionTabValue = "subscriptions";
|
||||
|
@ -24,9 +24,9 @@ import {
|
||||
QTUBE_PLAYLIST_BASE,
|
||||
QTUBE_VIDEO_BASE,
|
||||
} from "../constants/Identifiers.ts";
|
||||
import { allTabValue, subscriptionTabValue } from "../constants/Misc.ts";
|
||||
import { persistReducer } from "redux-persist";
|
||||
import { subscriptionListFilter } from "../App.tsx";
|
||||
import { ContentType, VideoListType } from "../state/features/persistSlice.ts";
|
||||
|
||||
export const useFetchVideos = () => {
|
||||
const dispatch = useDispatch();
|
||||
@ -194,15 +194,15 @@ export const useFetchVideos = () => {
|
||||
category?: string;
|
||||
subcategory?: string;
|
||||
keywords?: string;
|
||||
type?: string;
|
||||
contentType?: ContentType;
|
||||
};
|
||||
|
||||
const emptyFilters = {
|
||||
const emptyFilters: FilterType = {
|
||||
name: "",
|
||||
category: "",
|
||||
subcategory: "",
|
||||
keywords: "",
|
||||
type: "",
|
||||
contentType: "videos",
|
||||
};
|
||||
const getVideos = React.useCallback(
|
||||
async (
|
||||
@ -210,16 +210,16 @@ export const useFetchVideos = () => {
|
||||
reset?: boolean,
|
||||
resetFilters?: boolean,
|
||||
limit?: number,
|
||||
listType = allTabValue
|
||||
videoListType: VideoListType = "all"
|
||||
) => {
|
||||
emptyFilters.type = filters.type;
|
||||
emptyFilters.contentType = filters.contentType;
|
||||
try {
|
||||
const {
|
||||
name = "",
|
||||
category = "",
|
||||
subcategory = "",
|
||||
keywords = "",
|
||||
type = filters.type,
|
||||
contentType = filters.contentType,
|
||||
}: FilterType = resetFilters ? emptyFilters : filters;
|
||||
let offset = videos.length;
|
||||
if (reset) {
|
||||
@ -231,9 +231,7 @@ export const useFetchVideos = () => {
|
||||
|
||||
if (name) {
|
||||
defaultUrl = defaultUrl + `&name=${name}`;
|
||||
}
|
||||
|
||||
if (listType === subscriptionTabValue) {
|
||||
} else if (videoListType === "subscriptions") {
|
||||
const filteredSubscribeList = await subscriptionListFilter(false);
|
||||
filteredSubscribeList.map(sub => {
|
||||
defaultUrl += `&name=${sub.subscriberName}`;
|
||||
@ -252,7 +250,7 @@ export const useFetchVideos = () => {
|
||||
if (keywords) {
|
||||
defaultUrl = defaultUrl + `&query=${keywords}`;
|
||||
}
|
||||
if (type === "playlists") {
|
||||
if (contentType === "playlists") {
|
||||
defaultUrl = defaultUrl + `&service=PLAYLIST`;
|
||||
defaultUrl = defaultUrl + `&identifier=${QTUBE_PLAYLIST_BASE}`;
|
||||
} else {
|
||||
@ -431,9 +429,7 @@ export const useFetchVideos = () => {
|
||||
const totalVideosPublished = responseData.length;
|
||||
const uniqueNames = new Set(responseData.map(video => video.name));
|
||||
const totalNamesPublished = uniqueNames.size;
|
||||
const videosPerNamePublished = (
|
||||
totalVideosPublished / totalNamesPublished
|
||||
).toFixed(0);
|
||||
const videosPerNamePublished = totalVideosPublished / totalNamesPublished;
|
||||
|
||||
dispatch(setTotalVideosPublished(totalVideosPublished));
|
||||
dispatch(setTotalNamesPublished(totalNamesPublished));
|
||||
|
@ -1,34 +1,37 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { VideoListComponentLevel } from "../Home/VideoListComponentLevel";
|
||||
import { HeaderContainer, ProfileContainer } from "./Profile-styles";
|
||||
import { VideoListComponentLevel } from "../../Home/VideoListComponentLevel.tsx";
|
||||
import { HeaderContainer, ProfileContainer } from "./Profile-styles.tsx";
|
||||
import {
|
||||
AuthorTextComment,
|
||||
StyledCardColComment,
|
||||
StyledCardHeaderComment,
|
||||
} from "../VideoContent/VideoContent-styles";
|
||||
} from "../VideoContent/VideoContent-styles.tsx";
|
||||
import { Avatar, Box, useTheme } from "@mui/material";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import { setUserAvatarHash } from "../../state/features/globalSlice";
|
||||
import { RootState } from "../../state/store";
|
||||
import { SubscribeButton } from "../../components/common/SubscribeButton.tsx";
|
||||
import { setUserAvatarHash } from "../../../state/features/globalSlice.ts";
|
||||
import { RootState } from "../../../state/store.ts";
|
||||
import { SubscribeButton } from "../../../components/common/ContentButtons/SubscribeButton.tsx";
|
||||
import { FollowButton } from "../../../components/common/ContentButtons/FollowButton.tsx";
|
||||
|
||||
export const IndividualProfile = () => {
|
||||
const { name: paramName } = useParams();
|
||||
const { name: channelName } = useParams();
|
||||
const userName = useSelector((state: RootState) => state.auth.user?.name);
|
||||
|
||||
const userAvatarHash = useSelector(
|
||||
(state: RootState) => state.global.userAvatarHash
|
||||
);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const avatarUrl = useMemo(() => {
|
||||
let url = "";
|
||||
if (paramName && userAvatarHash[paramName]) {
|
||||
url = userAvatarHash[paramName];
|
||||
if (channelName && userAvatarHash[channelName]) {
|
||||
url = userAvatarHash[channelName];
|
||||
}
|
||||
|
||||
return url;
|
||||
}, [userAvatarHash, paramName]);
|
||||
}, [userAvatarHash, channelName]);
|
||||
return (
|
||||
<ProfileContainer>
|
||||
<HeaderContainer>
|
||||
@ -46,8 +49,8 @@ export const IndividualProfile = () => {
|
||||
>
|
||||
<Box>
|
||||
<Avatar
|
||||
src={`/arbitrary/THUMBNAIL/${paramName}/qortal_avatar`}
|
||||
alt={`${paramName}'s avatar`}
|
||||
src={`/arbitrary/THUMBNAIL/${channelName}/qortal_avatar`}
|
||||
alt={`${channelName}'s avatar`}
|
||||
/>
|
||||
</Box>
|
||||
<StyledCardColComment>
|
||||
@ -58,13 +61,21 @@ export const IndividualProfile = () => {
|
||||
: "#d6e8ff"
|
||||
}
|
||||
>
|
||||
{paramName}
|
||||
{channelName}
|
||||
</AuthorTextComment>
|
||||
</StyledCardColComment>
|
||||
<SubscribeButton
|
||||
subscriberName={paramName}
|
||||
sx={{ marginLeft: "10px" }}
|
||||
/>
|
||||
{channelName !== userName && (
|
||||
<>
|
||||
<SubscribeButton
|
||||
subscriberName={channelName}
|
||||
sx={{ marginLeft: "10px" }}
|
||||
/>
|
||||
<FollowButton
|
||||
followerName={channelName}
|
||||
sx={{ marginLeft: "20px" }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</StyledCardHeaderComment>
|
||||
</Box>
|
||||
</HeaderContainer>
|
@ -7,15 +7,18 @@ import React, {
|
||||
} from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { setIsLoadingGlobal } from "../../state/features/globalSlice";
|
||||
import { setIsLoadingGlobal } from "../../../state/features/globalSlice.ts";
|
||||
import { Avatar, Box, Typography, useTheme } from "@mui/material";
|
||||
import { VideoPlayer } from "../../components/common/VideoPlayer/VideoPlayer.tsx";
|
||||
import { RootState } from "../../state/store";
|
||||
import { addToHashMap } from "../../state/features/videoSlice";
|
||||
import {
|
||||
refType,
|
||||
VideoPlayer,
|
||||
} from "../../../components/common/VideoPlayer/VideoPlayer.tsx";
|
||||
import { RootState } from "../../../state/store.ts";
|
||||
import { addToHashMap } from "../../../state/features/videoSlice.ts";
|
||||
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
|
||||
import mockImg from "../../test/mockimg.jpg";
|
||||
import mockImg from "../../../test/mockimg.jpg";
|
||||
import {
|
||||
AuthorTextComment,
|
||||
FileAttachmentContainer,
|
||||
@ -26,39 +29,43 @@ import {
|
||||
VideoDescription,
|
||||
VideoPlayerContainer,
|
||||
VideoTitle,
|
||||
} from "./PlaylistContent-styles";
|
||||
import { setUserAvatarHash } from "../../state/features/globalSlice";
|
||||
} from "./PlaylistContent-styles.tsx";
|
||||
import { setUserAvatarHash } from "../../../state/features/globalSlice.ts";
|
||||
import {
|
||||
formatDate,
|
||||
formatDateSeconds,
|
||||
formatTimestampSeconds,
|
||||
} from "../../utils/time";
|
||||
import { NavbarName } from "../../components/layout/Navbar/Navbar-styles";
|
||||
import { CommentSection } from "../../components/common/Comments/CommentSection";
|
||||
} from "../../../utils/time.ts";
|
||||
import { NavbarName } from "../../../components/layout/Navbar/Navbar-styles.tsx";
|
||||
import { CommentSection } from "../../../components/common/Comments/CommentSection.tsx";
|
||||
import {
|
||||
CrowdfundSubTitle,
|
||||
CrowdfundSubTitleRow,
|
||||
} from "../../components/PublishVideo/PublishVideo-styles.tsx";
|
||||
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";
|
||||
} from "../../../components/Publish/PublishVideo/PublishVideo-styles.tsx";
|
||||
import { Playlists } from "../../../components/Playlists/Playlists.tsx";
|
||||
import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.tsx";
|
||||
import FileElement from "../../../components/common/FileElement.tsx";
|
||||
import { SuperLike } from "../../../components/common/ContentButtons/SuperLike.tsx";
|
||||
import { useFetchSuperLikes } from "../../../hooks/useFetchSuperLikes.tsx";
|
||||
import {
|
||||
extractSigValue,
|
||||
getPaymentInfo,
|
||||
isTimestampWithinRange,
|
||||
} from "../VideoContent/VideoContent";
|
||||
import { SuperLikesSection } from "../../components/common/SuperLikesList/SuperLikesSection";
|
||||
} from "../VideoContent/VideoContent.tsx";
|
||||
import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx";
|
||||
import {
|
||||
QTUBE_VIDEO_BASE,
|
||||
SUPER_LIKE_BASE,
|
||||
} from "../../constants/Identifiers.ts";
|
||||
import { minPriceSuperlike } from "../../constants/Misc.ts";
|
||||
import { SubscribeButton } from "../../components/common/SubscribeButton.tsx";
|
||||
} from "../../../constants/Identifiers.ts";
|
||||
import { minPriceSuperlike } from "../../../constants/Misc.ts";
|
||||
import { SubscribeButton } from "../../../components/common/ContentButtons/SubscribeButton.tsx";
|
||||
import { FollowButton } from "../../../components/common/ContentButtons/FollowButton.tsx";
|
||||
import { LikeAndDislike } from "../../../components/common/ContentButtons/LikeAndDislike.tsx";
|
||||
|
||||
export const PlaylistContent = () => {
|
||||
const { name, id } = useParams();
|
||||
const { name: channelName, id } = useParams();
|
||||
const userName = useSelector((state: RootState) => state.auth.user?.name);
|
||||
|
||||
const [doAutoPlay, setDoAutoPlay] = useState(false);
|
||||
const [isExpandedDescription, setIsExpandedDescription] =
|
||||
useState<boolean>(false);
|
||||
@ -68,6 +75,7 @@ export const PlaylistContent = () => {
|
||||
const [superlikeList, setSuperlikelist] = useState<any[]>([]);
|
||||
const [loadingSuperLikes, setLoadingSuperLikes] = useState<boolean>(false);
|
||||
const { addSuperlikeRawDataGetToList } = useFetchSuperLikes();
|
||||
const containerRef = useRef<refType>(null);
|
||||
|
||||
const calculateAmountSuperlike = useMemo(() => {
|
||||
const totalQort = superlikeList?.reduce((acc, curr) => {
|
||||
@ -95,10 +103,10 @@ export const PlaylistContent = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (name) {
|
||||
getAddressName(name);
|
||||
if (channelName) {
|
||||
getAddressName(channelName);
|
||||
}
|
||||
}, [name]);
|
||||
}, [channelName]);
|
||||
|
||||
const userAvatarHash = useSelector(
|
||||
(state: RootState) => state.global.userAvatarHash
|
||||
@ -107,12 +115,12 @@ export const PlaylistContent = () => {
|
||||
|
||||
const avatarUrl = useMemo(() => {
|
||||
let url = "";
|
||||
if (name && userAvatarHash[name]) {
|
||||
url = userAvatarHash[name];
|
||||
if (channelName && userAvatarHash[channelName]) {
|
||||
url = userAvatarHash[channelName];
|
||||
}
|
||||
|
||||
return url;
|
||||
}, [userAvatarHash, name]);
|
||||
}, [userAvatarHash, channelName]);
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
|
||||
@ -282,10 +290,10 @@ export const PlaylistContent = () => {
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (name && id) {
|
||||
checkforPlaylist(name, id);
|
||||
if (channelName && id) {
|
||||
checkforPlaylist(channelName, id);
|
||||
}
|
||||
}, [id, name]);
|
||||
}, [id, channelName]);
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current) {
|
||||
@ -395,6 +403,14 @@ export const PlaylistContent = () => {
|
||||
getComments(videoData?.id, nameAddress);
|
||||
}, [getComments, videoData?.id, nameAddress]);
|
||||
|
||||
const focusVideo = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const focusRef = containerRef.current?.getContainerRef()?.current;
|
||||
const isCorrectTarget = e.currentTarget == e.target;
|
||||
if (focusRef && isCorrectTarget) {
|
||||
focusRef.focus({ preventScroll: true });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@ -403,6 +419,7 @@ export const PlaylistContent = () => {
|
||||
flexDirection: "column",
|
||||
padding: "20px 10px",
|
||||
}}
|
||||
onClick={focusVideo}
|
||||
>
|
||||
<VideoPlayerContainer
|
||||
sx={{
|
||||
@ -434,13 +451,14 @@ export const PlaylistContent = () => {
|
||||
name={videoReference?.name}
|
||||
service={videoReference?.service}
|
||||
identifier={videoReference?.identifier}
|
||||
user={name}
|
||||
user={channelName}
|
||||
jsonId={id}
|
||||
poster={videoCover || ""}
|
||||
nextVideo={nextVideo}
|
||||
onEnd={onEndVideo}
|
||||
autoPlay={doAutoPlay}
|
||||
customStyle={{ aspectRatio: "16/9" }}
|
||||
ref={containerRef}
|
||||
/>
|
||||
)}
|
||||
{playlistData && (
|
||||
@ -473,12 +491,12 @@ export const PlaylistContent = () => {
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => {
|
||||
navigate(`/channel/${name}`);
|
||||
navigate(`/channel/${channelName}`);
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
src={`/arbitrary/THUMBNAIL/${name}/qortal_avatar`}
|
||||
alt={`${name}'s avatar`}
|
||||
src={`/arbitrary/THUMBNAIL/${channelName}/qortal_avatar`}
|
||||
alt={`${channelName}'s avatar`}
|
||||
/>
|
||||
</Box>
|
||||
<StyledCardColComment>
|
||||
@ -492,14 +510,22 @@ export const PlaylistContent = () => {
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => {
|
||||
navigate(`/channel/${name}`);
|
||||
navigate(`/channel/${channelName}`);
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
<SubscribeButton
|
||||
subscriberName={name}
|
||||
sx={{ marginLeft: "20px" }}
|
||||
/>
|
||||
{channelName}
|
||||
{channelName !== userName && (
|
||||
<>
|
||||
<SubscribeButton
|
||||
subscriberName={channelName}
|
||||
sx={{ marginLeft: "20px" }}
|
||||
/>
|
||||
<FollowButton
|
||||
followerName={channelName}
|
||||
sx={{ marginLeft: "20px" }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</AuthorTextComment>
|
||||
</StyledCardColComment>
|
||||
</StyledCardHeaderComment>
|
||||
@ -511,16 +537,22 @@ export const PlaylistContent = () => {
|
||||
}}
|
||||
>
|
||||
{videoData && (
|
||||
<SuperLike
|
||||
numberOfSuperlikes={numberOfSuperlikes}
|
||||
totalAmount={calculateAmountSuperlike}
|
||||
name={videoData?.user}
|
||||
service={videoData?.service}
|
||||
identifier={videoData?.id}
|
||||
onSuccess={val => {
|
||||
setSuperlikelist(prev => [val, ...prev]);
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
<LikeAndDislike
|
||||
name={videoData?.user}
|
||||
identifier={videoData?.id}
|
||||
/>
|
||||
<SuperLike
|
||||
numberOfSuperlikes={numberOfSuperlikes}
|
||||
totalAmount={calculateAmountSuperlike}
|
||||
name={videoData?.user}
|
||||
service={videoData?.service}
|
||||
identifier={videoData?.id}
|
||||
onSuccess={val => {
|
||||
setSuperlikelist(prev => [val, ...prev]);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<FileAttachmentContainer>
|
||||
<FileAttachmentFont>Save to Disk</FileAttachmentFont>
|
||||
@ -677,7 +709,10 @@ export const PlaylistContent = () => {
|
||||
maxWidth: "1200px",
|
||||
}}
|
||||
>
|
||||
<CommentSection postId={videoData?.id || ""} postName={name || ""} />
|
||||
<CommentSection
|
||||
postId={videoData?.id || ""}
|
||||
postName={channelName || ""}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
@ -7,15 +7,18 @@ import React, {
|
||||
} from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { setIsLoadingGlobal } from "../../state/features/globalSlice";
|
||||
import { setIsLoadingGlobal } from "../../../state/features/globalSlice.ts";
|
||||
import { Avatar, Box, Typography, useTheme } from "@mui/material";
|
||||
import { VideoPlayer } from "../../components/common/VideoPlayer/VideoPlayer.tsx";
|
||||
import { RootState } from "../../state/store";
|
||||
import { addToHashMap } from "../../state/features/videoSlice";
|
||||
import {
|
||||
refType,
|
||||
VideoPlayer,
|
||||
} from "../../../components/common/VideoPlayer/VideoPlayer.tsx";
|
||||
import { RootState } from "../../../state/store.ts";
|
||||
import { addToHashMap } from "../../../state/features/videoSlice.ts";
|
||||
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
|
||||
import mockImg from "../../test/mockimg.jpg";
|
||||
import mockImg from "../../../test/mockimg.jpg";
|
||||
import {
|
||||
AuthorTextComment,
|
||||
FileAttachmentContainer,
|
||||
@ -26,37 +29,39 @@ import {
|
||||
VideoDescription,
|
||||
VideoPlayerContainer,
|
||||
VideoTitle,
|
||||
} from "./VideoContent-styles";
|
||||
import { setUserAvatarHash } from "../../state/features/globalSlice";
|
||||
} from "./VideoContent-styles.tsx";
|
||||
import { setUserAvatarHash } from "../../../state/features/globalSlice.ts";
|
||||
import {
|
||||
formatDate,
|
||||
formatDateSeconds,
|
||||
formatTimestampSeconds,
|
||||
} from "../../utils/time";
|
||||
import { NavbarName } from "../../components/layout/Navbar/Navbar-styles";
|
||||
import { CommentSection } from "../../components/common/Comments/CommentSection";
|
||||
} from "../../../utils/time.ts";
|
||||
import { NavbarName } from "../../../components/layout/Navbar/Navbar-styles.tsx";
|
||||
import { CommentSection } from "../../../components/common/Comments/CommentSection.tsx";
|
||||
import {
|
||||
CrowdfundSubTitle,
|
||||
CrowdfundSubTitleRow,
|
||||
} from "../../components/PublishVideo/PublishVideo-styles.tsx";
|
||||
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 { 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";
|
||||
} from "../../../components/Publish/PublishVideo/PublishVideo-styles.tsx";
|
||||
import { Playlists } from "../../../components/Playlists/Playlists.tsx";
|
||||
import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.tsx";
|
||||
import FileElement from "../../../components/common/FileElement.tsx";
|
||||
import { SuperLike } from "../../../components/common/ContentButtons/SuperLike.tsx";
|
||||
import { CommentContainer } from "../../../components/common/Comments/Comments-styles.tsx";
|
||||
import { Comment } from "../../../components/common/Comments/Comment.tsx";
|
||||
import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx";
|
||||
import { useFetchSuperLikes } from "../../../hooks/useFetchSuperLikes.tsx";
|
||||
import {
|
||||
FOR_SUPER_LIKE,
|
||||
QTUBE_VIDEO_BASE,
|
||||
SUPER_LIKE_BASE,
|
||||
} from "../../constants/Identifiers.ts";
|
||||
} from "../../../constants/Identifiers.ts";
|
||||
import {
|
||||
minPriceSuperlike,
|
||||
titleFormatterOnSave,
|
||||
} from "../../constants/Misc.ts";
|
||||
import { SubscribeButton } from "../../components/common/SubscribeButton.tsx";
|
||||
} from "../../../constants/Misc.ts";
|
||||
import { SubscribeButton } from "../../../components/common/ContentButtons/SubscribeButton.tsx";
|
||||
import { FollowButton } from "../../../components/common/ContentButtons/FollowButton.tsx";
|
||||
import { LikeAndDislike } from "../../../components/common/ContentButtons/LikeAndDislike.tsx";
|
||||
|
||||
export function isTimestampWithinRange(resTimestamp, resCreated) {
|
||||
// Calculate the absolute difference in milliseconds
|
||||
@ -116,12 +121,15 @@ export const getPaymentInfo = async (signature: string) => {
|
||||
};
|
||||
|
||||
export const VideoContent = () => {
|
||||
const { name, id } = useParams();
|
||||
const { name: channelName, id } = useParams();
|
||||
const userName = useSelector((state: RootState) => state.auth.user?.name);
|
||||
|
||||
const [isExpandedDescription, setIsExpandedDescription] =
|
||||
useState<boolean>(false);
|
||||
const [superlikeList, setSuperlikelist] = useState<any[]>([]);
|
||||
const [loadingSuperLikes, setLoadingSuperLikes] = useState<boolean>(false);
|
||||
const { addSuperlikeRawDataGetToList } = useFetchSuperLikes();
|
||||
const containerRef = useRef<refType>(null);
|
||||
|
||||
const calculateAmountSuperlike = useMemo(() => {
|
||||
const totalQort = superlikeList?.reduce((acc, curr) => {
|
||||
@ -143,6 +151,7 @@ export const VideoContent = () => {
|
||||
const userAvatarHash = useSelector(
|
||||
(state: RootState) => state.global.userAvatarHash
|
||||
);
|
||||
|
||||
const contentRef = useRef(null);
|
||||
|
||||
const getAddressName = async name => {
|
||||
@ -157,18 +166,18 @@ export const VideoContent = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (name) {
|
||||
getAddressName(name);
|
||||
if (channelName) {
|
||||
getAddressName(channelName);
|
||||
}
|
||||
}, [name]);
|
||||
}, [channelName]);
|
||||
const avatarUrl = useMemo(() => {
|
||||
let url = "";
|
||||
if (name && userAvatarHash[name]) {
|
||||
url = userAvatarHash[name];
|
||||
if (channelName && userAvatarHash[channelName]) {
|
||||
url = userAvatarHash[channelName];
|
||||
}
|
||||
|
||||
return url;
|
||||
}, [userAvatarHash, name]);
|
||||
}, [userAvatarHash, channelName]);
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
|
||||
@ -279,16 +288,16 @@ export const VideoContent = () => {
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (name && id) {
|
||||
const existingVideo = hashMapVideos[id + "-" + name];
|
||||
if (channelName && id) {
|
||||
const existingVideo = hashMapVideos[id + "-" + channelName];
|
||||
|
||||
if (existingVideo) {
|
||||
setVideoData(existingVideo);
|
||||
} else {
|
||||
getVideoData(name, id);
|
||||
getVideoData(channelName, id);
|
||||
}
|
||||
}
|
||||
}, [id, name]);
|
||||
}, [id, channelName]);
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current) {
|
||||
@ -367,6 +376,14 @@ export const VideoContent = () => {
|
||||
(state: RootState) => state.persist.subscriptionList
|
||||
);
|
||||
|
||||
const focusVideo = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const focusRef = containerRef.current?.getContainerRef()?.current;
|
||||
const isCorrectTarget = e.currentTarget == e.target;
|
||||
if (focusRef && isCorrectTarget) {
|
||||
focusRef.focus({ preventScroll: true });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@ -375,6 +392,7 @@ export const VideoContent = () => {
|
||||
flexDirection: "column",
|
||||
padding: "20px 10px",
|
||||
}}
|
||||
onClick={focusVideo}
|
||||
>
|
||||
<VideoPlayerContainer
|
||||
sx={{
|
||||
@ -387,10 +405,11 @@ export const VideoContent = () => {
|
||||
name={videoReference?.name}
|
||||
service={videoReference?.service}
|
||||
identifier={videoReference?.identifier}
|
||||
user={name}
|
||||
user={channelName}
|
||||
jsonId={id}
|
||||
poster={videoCover || ""}
|
||||
customStyle={{ aspectRatio: "16/9" }}
|
||||
ref={containerRef}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
@ -414,12 +433,12 @@ export const VideoContent = () => {
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => {
|
||||
navigate(`/channel/${name}`);
|
||||
navigate(`/channel/${channelName}`);
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
src={`/arbitrary/THUMBNAIL/${name}/qortal_avatar`}
|
||||
alt={`${name}'s avatar`}
|
||||
src={`/arbitrary/THUMBNAIL/${channelName}/qortal_avatar`}
|
||||
alt={`${channelName}'s avatar`}
|
||||
/>
|
||||
</Box>
|
||||
<StyledCardColComment>
|
||||
@ -433,14 +452,22 @@ export const VideoContent = () => {
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => {
|
||||
navigate(`/channel/${name}`);
|
||||
navigate(`/channel/${channelName}`);
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
<SubscribeButton
|
||||
subscriberName={name}
|
||||
sx={{ marginLeft: "20px" }}
|
||||
/>
|
||||
{channelName}
|
||||
{channelName !== userName && (
|
||||
<>
|
||||
<SubscribeButton
|
||||
subscriberName={channelName}
|
||||
sx={{ marginLeft: "20px" }}
|
||||
/>
|
||||
<FollowButton
|
||||
followerName={channelName}
|
||||
sx={{ marginLeft: "20px" }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</AuthorTextComment>
|
||||
</StyledCardColComment>
|
||||
</StyledCardHeaderComment>
|
||||
@ -452,16 +479,22 @@ export const VideoContent = () => {
|
||||
}}
|
||||
>
|
||||
{videoData && (
|
||||
<SuperLike
|
||||
numberOfSuperlikes={numberOfSuperlikes}
|
||||
totalAmount={calculateAmountSuperlike}
|
||||
name={videoData?.user}
|
||||
service={videoData?.service}
|
||||
identifier={videoData?.id}
|
||||
onSuccess={val => {
|
||||
setSuperlikelist(prev => [val, ...prev]);
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
<LikeAndDislike
|
||||
name={videoData?.user}
|
||||
identifier={videoData?.id}
|
||||
/>
|
||||
<SuperLike
|
||||
numberOfSuperlikes={numberOfSuperlikes}
|
||||
totalAmount={calculateAmountSuperlike}
|
||||
name={videoData?.user}
|
||||
service={videoData?.service}
|
||||
identifier={videoData?.id}
|
||||
onSuccess={val => {
|
||||
setSuperlikelist(prev => [val, ...prev]);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<FileAttachmentContainer>
|
||||
<FileAttachmentFont>Save to Disk</FileAttachmentFont>
|
||||
@ -598,7 +631,7 @@ export const VideoContent = () => {
|
||||
loadingSuperLikes={loadingSuperLikes}
|
||||
superlikes={superlikeList}
|
||||
postId={id || ""}
|
||||
postName={name || ""}
|
||||
postName={channelName || ""}
|
||||
/>
|
||||
|
||||
<Box
|
||||
@ -609,7 +642,7 @@ export const VideoContent = () => {
|
||||
maxWidth: "1200px",
|
||||
}}
|
||||
>
|
||||
<CommentSection postId={id || ""} postName={name || ""} />
|
||||
<CommentSection postId={id || ""} postName={channelName || ""} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
@ -37,12 +37,12 @@ import {
|
||||
import {
|
||||
changeFilterType,
|
||||
resetSubscriptions,
|
||||
VideoListType,
|
||||
} from "../../state/features/persistSlice.ts";
|
||||
import { categories, subCategories } from "../../constants/Categories.ts";
|
||||
import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx";
|
||||
import { TabContext, TabList, TabPanel } from "@mui/lab";
|
||||
import VideoList from "./VideoList.tsx";
|
||||
import { allTabValue, subscriptionTabValue } from "../../constants/Misc.ts";
|
||||
import { setHomePageSelectedTab } from "../../state/features/persistSlice.ts";
|
||||
import { StatsData } from "../../components/StatsData.tsx";
|
||||
|
||||
@ -74,7 +74,9 @@ export const Home = ({ mode }: HomeProps) => {
|
||||
);
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [tabValue, setTabValue] = useState<string>(persistReducer.selectedTab);
|
||||
const [tabValue, setTabValue] = useState<VideoListType>(
|
||||
persistReducer.selectedTab
|
||||
);
|
||||
|
||||
const tabFontSize = "20px";
|
||||
|
||||
@ -123,14 +125,14 @@ export const Home = ({ mode }: HomeProps) => {
|
||||
if (!firstFetch.current || !afterFetch.current) return;
|
||||
if (isFetching.current) return;
|
||||
isFetching.current = true;
|
||||
console.log("in getvideoshandler");
|
||||
|
||||
await getVideos(
|
||||
{
|
||||
name: filterName,
|
||||
category: selectedCategoryVideos?.id,
|
||||
subcategory: selectedSubCategoryVideos?.id,
|
||||
keywords: filterSearch,
|
||||
type: filterType,
|
||||
contentType: filterType,
|
||||
},
|
||||
reset,
|
||||
resetFilters,
|
||||
@ -171,7 +173,7 @@ export const Home = ({ mode }: HomeProps) => {
|
||||
category: "",
|
||||
subcategory: "",
|
||||
keywords: "",
|
||||
type: filterType,
|
||||
contentType: filterType,
|
||||
},
|
||||
null,
|
||||
null,
|
||||
@ -269,11 +271,10 @@ export const Home = ({ mode }: HomeProps) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log("useeffect 5");
|
||||
getVideosHandler(true);
|
||||
}, [tabValue]);
|
||||
|
||||
const changeTab = (e: React.SyntheticEvent, newValue: string) => {
|
||||
const changeTab = (e: React.SyntheticEvent, newValue: VideoListType) => {
|
||||
setTabValue(newValue);
|
||||
dispatch(setHomePageSelectedTab(newValue));
|
||||
};
|
||||
@ -516,23 +517,23 @@ export const Home = ({ mode }: HomeProps) => {
|
||||
>
|
||||
<Tab
|
||||
label="All Videos"
|
||||
value={allTabValue}
|
||||
value={"all"}
|
||||
sx={{ fontSize: tabFontSize }}
|
||||
/>
|
||||
<Tab
|
||||
label="Subscriptions"
|
||||
value={subscriptionTabValue}
|
||||
value={"subscriptions"}
|
||||
sx={{ fontSize: tabFontSize }}
|
||||
/>
|
||||
</TabList>
|
||||
<TabPanel value={allTabValue} sx={{ width: "100%" }}>
|
||||
<TabPanel value={"all"} sx={{ width: "100%" }}>
|
||||
<VideoList videos={videos} />
|
||||
<LazyLoad
|
||||
onLoadMore={getVideosHandler}
|
||||
isLoading={isLoading}
|
||||
></LazyLoad>
|
||||
</TabPanel>
|
||||
<TabPanel value={subscriptionTabValue} sx={{ width: "100%" }}>
|
||||
<TabPanel value={"subscriptions"} sx={{ width: "100%" }}>
|
||||
{filteredSubscriptionList.length > 0 ? (
|
||||
<>
|
||||
<VideoList videos={videos} />
|
||||
@ -541,10 +542,12 @@ export const Home = ({ mode }: HomeProps) => {
|
||||
isLoading={isLoading}
|
||||
></LazyLoad>
|
||||
</>
|
||||
) : (
|
||||
) : !isLoading ? (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
You have no subscriptions
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</TabPanel>
|
||||
</TabContext>
|
||||
|
@ -1,14 +1,19 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { allTabValue, subscriptionTabValue } from "../../constants/Misc.ts";
|
||||
import { SubscriptionData } from "../../components/common/SubscribeButton.tsx";
|
||||
|
||||
type StretchVideoType = "contain" | "fill" | "cover" | "none" | "scale-down";
|
||||
type SubscriptionListFilterType = "ALL" | "currentNameOnly";
|
||||
import { SubscriptionData } from "../../components/common/ContentButtons/SubscribeButton.tsx";
|
||||
|
||||
export type StretchVideoType =
|
||||
| "contain"
|
||||
| "fill"
|
||||
| "cover"
|
||||
| "none"
|
||||
| "scale-down";
|
||||
export type SubscriptionListFilterType = "ALL" | "currentNameOnly";
|
||||
export type ContentType = "videos" | "playlists";
|
||||
export type VideoListType = "all" | "subscriptions";
|
||||
interface settingsState {
|
||||
selectedTab: string;
|
||||
selectedTab: VideoListType;
|
||||
stretchVideoSetting: StretchVideoType;
|
||||
filterType: string;
|
||||
filterType: ContentType;
|
||||
subscriptionList: SubscriptionData[];
|
||||
playbackRate: number;
|
||||
subscriptionListFilter: SubscriptionListFilterType;
|
||||
@ -16,7 +21,7 @@ interface settingsState {
|
||||
}
|
||||
|
||||
const initialState: settingsState = {
|
||||
selectedTab: allTabValue,
|
||||
selectedTab: "all",
|
||||
stretchVideoSetting: "contain",
|
||||
filterType: "videos",
|
||||
subscriptionList: [],
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { SubscriptionData } from "../../components/common/SubscribeButton";
|
||||
import { SubscriptionData } from "../../components/common/ContentButtons/SubscribeButton.tsx";
|
||||
|
||||
interface GlobalState {
|
||||
videos: Video[];
|
||||
|
@ -2,6 +2,7 @@ import {
|
||||
AccountInfo,
|
||||
AccountName,
|
||||
GetRequestData,
|
||||
SearchResourcesResponse,
|
||||
SearchTransactionResponse,
|
||||
TransactionSearchParams,
|
||||
} from "./qortalRequestTypes.ts";
|
||||
@ -46,3 +47,30 @@ export const searchTransactions = async (params: TransactionSearchParams) => {
|
||||
...params,
|
||||
})) as SearchTransactionResponse[];
|
||||
};
|
||||
|
||||
export const fetchResourcesByIdentifier = async <T>(
|
||||
service: string,
|
||||
identifier: string
|
||||
) => {
|
||||
const names: SearchResourcesResponse[] = await qortalRequest({
|
||||
action: "SEARCH_QDN_RESOURCES",
|
||||
service,
|
||||
identifier,
|
||||
includeMetadata: false,
|
||||
});
|
||||
const distinctNames = names.filter(
|
||||
(searchResponse, index) => names.indexOf(searchResponse) === index
|
||||
);
|
||||
|
||||
const promises: Promise<T>[] = [];
|
||||
distinctNames.map(response => {
|
||||
const resource: Promise<T> = qortalRequest({
|
||||
action: "FETCH_QDN_RESOURCE",
|
||||
name: response.name,
|
||||
service,
|
||||
identifier,
|
||||
});
|
||||
promises.push(resource);
|
||||
});
|
||||
return (await Promise.all(promises)) as T[];
|
||||
};
|
||||
|
@ -23,6 +23,23 @@ export interface SearchTransactionResponse {
|
||||
amount: string;
|
||||
}
|
||||
|
||||
export interface MetaData {
|
||||
title: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
mimeType: string;
|
||||
}
|
||||
|
||||
export interface SearchResourcesResponse {
|
||||
name: string;
|
||||
service: string;
|
||||
identifier: string;
|
||||
metadata?: MetaData;
|
||||
size: number;
|
||||
created: number;
|
||||
updated: number;
|
||||
}
|
||||
|
||||
export type TransactionType =
|
||||
| "GENESIS"
|
||||
| "PAYMENT"
|
||||
|
@ -18,14 +18,14 @@ import {
|
||||
import { VideoPlayerGlobal } from "../components/common/VideoPlayer/VideoPlayerGlobal.tsx";
|
||||
import { Rnd } from "react-rnd";
|
||||
import { RequestQueue } from "../utils/queue";
|
||||
import { EditVideo } from "../components/EditVideo/EditVideo";
|
||||
import { EditPlaylist } from "../components/EditPlaylist/EditPlaylist";
|
||||
import { EditVideo } from "../components/Publish/EditVideo/EditVideo";
|
||||
import { EditPlaylist } from "../components/Publish/EditPlaylist/EditPlaylist";
|
||||
import ConsentModal from "../components/common/ConsentModal";
|
||||
import {
|
||||
extractSigValue,
|
||||
getPaymentInfo,
|
||||
isTimestampWithinRange,
|
||||
} from "../pages/VideoContent/VideoContent";
|
||||
} from "../pages/ContentPages/VideoContent/VideoContent";
|
||||
import { useFetchSuperLikes } from "../hooks/useFetchSuperLikes";
|
||||
import { SUPER_LIKE_BASE } from "../constants/Identifiers.ts";
|
||||
import { minPriceSuperlike } from "../constants/Misc.ts";
|
||||
@ -143,8 +143,8 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
|
||||
|
||||
const getSuperlikes = useCallback(async () => {
|
||||
try {
|
||||
let totalCount = 0
|
||||
let validCount = 0
|
||||
let totalCount = 0;
|
||||
let validCount = 0;
|
||||
let comments: any[] = [];
|
||||
while (validCount < 20 && totalCount < 100) {
|
||||
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&query=${SUPER_LIKE_BASE}&limit=1&offset=${totalCount}&includemetadata=true&reverse=true&excludeblocked=true`;
|
||||
@ -177,12 +177,12 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
|
||||
});
|
||||
comments = [
|
||||
...comments,
|
||||
{
|
||||
...comment,
|
||||
message: "",
|
||||
amount: res.amount,
|
||||
},
|
||||
];
|
||||
{
|
||||
...comment,
|
||||
message: "",
|
||||
amount: res.amount,
|
||||
},
|
||||
];
|
||||
validCount++;
|
||||
}
|
||||
} catch (error) {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user