Browse Source

Follow, Like, and Dislike buttons Added to Video, Playlist, and Channel pages

The Subscribe and Follow buttons no longer appear when the user views their own videos

Minimum Superlike amount lowered from 10 to 1 QORT

Fixed bug that made searches in the Home page Subscriptions Tab return results from all videos.

Fixed bug that prevented filtering by name on Subscriptions Tab

Clicking in area around video gives it focus, allowing hotkeys to work, orange border around video when focused is removed

Subscription tab doesn't say "You have no subscriptions" while loading videos
pull/23/head
Qortal Dev 6 months ago
parent
commit
9cf2932d81
  1. 8
      src/App.tsx
  2. 2
      src/components/Playlists/Playlists.tsx
  3. 24
      src/components/Publish/EditPlaylist/EditPlaylist.tsx
  4. 28
      src/components/Publish/EditPlaylist/Upload-styles.tsx
  5. 28
      src/components/Publish/EditVideo/EditVideo-styles.tsx
  6. 24
      src/components/Publish/EditVideo/EditVideo.tsx
  7. 4
      src/components/Publish/MultiplePublish/MultiplePublishAll.tsx
  8. 8
      src/components/Publish/PlaylistListEdit/PlaylistListEdit.tsx
  9. 2
      src/components/Publish/PublishVideo/PublishVideo-styles.tsx
  10. 32
      src/components/Publish/PublishVideo/PublishVideo.tsx
  11. 7
      src/components/StatsData.tsx
  12. 2
      src/components/common/Comments/CommentSection.tsx
  13. 159
      src/components/common/ContentButtons/FollowButton.tsx
  14. 66
      src/components/common/ContentButtons/LikeAndDislike-functions.ts
  15. 230
      src/components/common/ContentButtons/LikeAndDislike.tsx
  16. 48
      src/components/common/ContentButtons/SubscribeButton.tsx
  17. 97
      src/components/common/ContentButtons/SuperLike.tsx
  18. 2
      src/components/common/Notifications/Notifications.tsx
  19. 2
      src/components/common/SuperLikesList/SuperLikesSection.tsx
  20. 25
      src/components/common/VideoPlayer/VideoPlayer-styles.ts
  21. 1759
      src/components/common/VideoPlayer/VideoPlayer.tsx
  22. 4
      src/components/layout/Navbar/Navbar.tsx
  23. 11
      src/constants/Categories.ts
  24. 4
      src/constants/Identifiers.ts
  25. 5
      src/constants/Misc.ts
  26. 24
      src/hooks/useFetchVideos.tsx
  27. 45
      src/pages/ContentPages/IndividualProfile/IndividualProfile.tsx
  28. 0
      src/pages/ContentPages/IndividualProfile/Profile-styles.tsx
  29. 0
      src/pages/ContentPages/PlaylistContent/PlaylistContent-styles.tsx
  30. 139
      src/pages/ContentPages/PlaylistContent/PlaylistContent.tsx
  31. 0
      src/pages/ContentPages/VideoContent/VideoContent-styles.tsx
  32. 143
      src/pages/ContentPages/VideoContent/VideoContent.tsx
  33. 27
      src/pages/Home/Home.tsx
  34. 21
      src/state/features/persistSlice.ts
  35. 2
      src/state/features/videoSlice.ts
  36. 28
      src/utils/qortalRequestFunctions.ts
  37. 17
      src/utils/qortalRequestTypes.ts
  38. 22
      src/wrappers/GlobalWrapper.tsx

8
src/App.tsx

@ -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({

2
src/components/Playlists/Playlists.tsx

@ -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";

24
src/components/EditPlaylist/EditPlaylist.tsx → src/components/Publish/EditPlaylist/EditPlaylist.tsx

@ -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 });

28
src/components/EditPlaylist/Upload-styles.tsx → src/components/Publish/EditPlaylist/Upload-styles.tsx

@ -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",

28
src/components/EditVideo/EditVideo-styles.tsx → src/components/Publish/EditVideo/EditVideo-styles.tsx

@ -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",

24
src/components/EditVideo/EditVideo.tsx → src/components/Publish/EditVideo/EditVideo.tsx

@ -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 });

4
src/components/common/MultiplePublish/MultiplePublishAll.tsx → src/components/Publish/MultiplePublish/MultiplePublishAll.tsx

@ -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 {

8
src/components/PlaylistListEdit/PlaylistListEdit.tsx → src/components/Publish/PlaylistListEdit/PlaylistListEdit.tsx

@ -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();

2
src/components/PublishVideo/PublishVideo-styles.tsx → src/components/Publish/PublishVideo/PublishVideo-styles.tsx

@ -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;

32
src/components/PublishVideo/PublishVideo.tsx → src/components/Publish/PublishVideo/PublishVideo.tsx

@ -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) => {

7
src/components/StatsData.tsx

@ -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>
);

2
src/components/common/Comments/CommentSection.tsx

@ -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

@ -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>
</>
);
};

66
src/components/common/ContentButtons/LikeAndDislike-functions.ts

@ -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

@ -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>
</>
);
};

48
src/components/common/SubscribeButton.tsx → src/components/common/ContentButtons/SubscribeButton.tsx

@ -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>
);
};

97
src/components/common/SuperLike/SuperLike.tsx → src/components/common/ContentButtons/SuperLike.tsx

@ -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}

2
src/components/common/Notifications/Notifications.tsx

@ -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";

2
src/components/common/SuperLikesList/SuperLikesSection.tsx

@ -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 {

25
src/components/common/VideoPlayer/VideoPlayer-styles.ts

@ -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%;

1759
src/components/common/VideoPlayer/VideoPlayer.tsx

File diff suppressed because it is too large Load Diff

4
src/components/layout/Navbar/Navbar.tsx

@ -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;

11
src/constants/Categories.ts

@ -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),
};

4
src/constants/Identifiers.ts

@ -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`;

5
src/constants/Misc.ts

@ -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
src/hooks/useFetchVideos.tsx

@ -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));

45
src/pages/IndividualProfile/IndividualProfile.tsx → src/pages/ContentPages/IndividualProfile/IndividualProfile.tsx

@ -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>

0
src/pages/IndividualProfile/Profile-styles.tsx → src/pages/ContentPages/IndividualProfile/Profile-styles.tsx

0
src/pages/PlaylistContent/PlaylistContent-styles.tsx → src/pages/ContentPages/PlaylistContent/PlaylistContent-styles.tsx

139
src/pages/PlaylistContent/PlaylistContent.tsx → src/pages/ContentPages/PlaylistContent/PlaylistContent.tsx

@ -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>
);

0
src/pages/VideoContent/VideoContent-styles.tsx → src/pages/ContentPages/VideoContent/VideoContent-styles.tsx

143
src/pages/VideoContent/VideoContent.tsx → src/pages/ContentPages/VideoContent/VideoContent.tsx

@ -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>
);

27
src/pages/Home/Home.tsx

@ -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>

21
src/state/features/persistSlice.ts

@ -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: [],

2
src/state/features/videoSlice.ts

@ -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[];

28
src/utils/qortalRequestFunctions.ts

@ -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[];
};

17
src/utils/qortalRequestTypes.ts

@ -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"

22
src/wrappers/GlobalWrapper.tsx

@ -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…
Cancel
Save