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

Merge pull request #66 from QortalSeth/main

Superlikes list on Home Page shows time since it was made
This commit is contained in:
Qortal Dev 2025-01-23 10:48:09 -07:00 committed by GitHub
commit 55420dd17f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 458 additions and 24 deletions

View File

@ -0,0 +1,425 @@
import ThumbDownIcon from "@mui/icons-material/ThumbDown";
import {
Box,
DialogContent,
InputAdornment,
InputLabel,
Modal,
Tooltip,
useMediaQuery,
useTheme,
} from "@mui/material";
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import ShortUniqueId from "short-unique-id";
import qortImg from "../../../assets/img/qort.png";
import {
FOR,
FOR_SUPER_LIKE,
SUPER_LIKE_BASE,
} from "../../../constants/Identifiers.ts";
import {
fontSizeLarge,
fontSizeMedium,
minPriceSuperDislike,
} from "../../../constants/Misc.ts";
import { setNotification } from "../../../state/features/notificationsSlice.ts";
import { RootState } from "../../../state/store.ts";
import BoundedNumericTextField from "../../../utils/BoundedNumericTextField.tsx";
import { numberToInt, truncateNumber } from "../../../utils/numberFunctions.ts";
import { objectToBase64 } from "../../../utils/PublishFormatter.ts";
import { getUserBalance } from "../../../utils/qortalRequestFunctions.ts";
import { MultiplePublish } from "../../Publish/MultiplePublish/MultiplePublishAll.tsx";
import {
CrowdfundActionButton,
CrowdfundActionButtonRow,
ModalBody,
NewCrowdfundTitle,
Spacer,
} from "../../Publish/PublishVideo/PublishVideo-styles.tsx";
import { CommentInput } from "../Comments/Comments-styles.tsx";
const uid = new ShortUniqueId({ length: 4 });
export const SuperDislike = ({
onSuccess,
name,
service,
identifier,
totalAmount,
numberOfSuperdislikes,
}) => {
const [isOpen, setIsOpen] = useState<boolean>(false);
const [superDislikeDonationAmount, setSuperdislikeDonationAmount] =
useState<number>(minPriceSuperDislike);
const [currentBalance, setCurrentBalance] = useState<string>("");
const [comment, setComment] = useState<string>("");
const username = useSelector((state: RootState) => state.auth?.user?.name);
const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false);
const [publishes, setPublishes] = useState<any>(null);
const dispatch = useDispatch();
const resetValues = () => {
setSuperdislikeDonationAmount(0);
setComment("");
setPublishes(null);
};
const onClose = () => {
resetValues();
setIsOpen(false);
};
async function publishSuperDislike() {
try {
if (!username) throw new Error("You need a name to publish");
if (!name) throw new Error("Could not retrieve content creator's name");
const estimatedTransactionFees = 0.1;
const donationExceedsBalance =
superDislikeDonationAmount + estimatedTransactionFees >=
+currentBalance;
if (donationExceedsBalance) {
throw new Error("Total donations exceeds current balance");
}
const resName = await qortalRequest({
action: "GET_NAME_DATA",
name: name,
});
const address = resName.owner;
if (!identifier) throw new Error("Could not retrieve id of video post");
// if (comment.length > 200) throw new Error("Comment needs to be under 200 characters")
if (!address)
throw new Error("Could not retrieve content creator's address");
if (
!superDislikeDonationAmount ||
superDislikeDonationAmount < minPriceSuperDislike
)
throw new Error(
`The amount is ${superDislikeDonationAmount}, but it needs to be at least ${minPriceSuperDislike} QORT`
);
const listOfPublishes = [];
const res = await qortalRequest({
action: "SEND_COIN",
coin: "QORT",
destinationAddress: address,
amount: superDislikeDonationAmount,
});
const metadescription = `**sig:${
res.signature
};${FOR}:${name}_${FOR_SUPER_LIKE};nm:${name.slice(
0,
20
)};id:${identifier.slice(-30)}**`;
const id = uid.rnd();
const identifierSuperDislike = `${SUPER_LIKE_BASE}${identifier.slice(
0,
39
)}_${id}`;
const superLikeToBase64 = await objectToBase64({
comment,
transactionReference: res.signature,
notificationInformation: {
name,
identifier,
for: `${name}_${FOR_SUPER_LIKE}`,
},
about:
"Super likes are a way to 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);
const requestBodyJson: any = {
action: "PUBLISH_QDN_RESOURCE",
name: username,
service: "BLOG_COMMENT",
data64: superLikeToBase64,
title: "",
description: metadescription,
identifier: identifierSuperDislike,
tag1: SUPER_LIKE_BASE,
filename: `superDislike_metadata.json`,
};
listOfPublishes.push(requestBodyJson);
const multiplePublish = {
action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
resources: [...listOfPublishes],
};
setPublishes(multiplePublish);
setIsOpenMultiplePublish(true);
} catch (error: any) {
dispatch(
setNotification({
msg:
error?.error ||
error?.message ||
error ||
"Failed to publish Super Like",
alertType: "error",
})
);
throw new Error("Failed to publish Super Like");
}
}
useEffect(() => {
getUserBalance().then(foundBalance => {
setCurrentBalance(truncateNumber(foundBalance, 2));
});
}, []);
const isScreenSmall = !useMediaQuery(`(min-width:400px)`);
const theme = useTheme();
return (
<>
<Box
onClick={() => {
if (username === name) {
dispatch(
setNotification({
msg: "You cannot send yourself a Super like",
alertType: "error",
})
);
return;
}
setIsOpen(true);
}}
sx={{
display: "flex",
alignItems: "center",
gap: "15px",
cursor: "pointer",
flexShrink: 0,
}}
>
<Tooltip title="Super Like" placement="top">
<Box
sx={{
padding: "5px",
borderRadius: "7px",
gap: "5px",
display: "flex",
alignItems: "center",
outline: "1px red solid",
marginRight: "10px",
height: "53px",
}}
>
<ThumbDownIcon
style={{
color: "red",
}}
/>
{numberOfSuperdislikes === 0 ? null : (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
userSelect: "none",
}}
>
<span style={{ marginRight: "10px", paddingBottom: "4px" }}>
{numberOfSuperdislikes}
</span>
<img
style={{
height: "25px",
width: "25px",
marginRight: "5px",
}}
src={qortImg}
alt={"Qort Icon"}
/>
{truncateNumber(totalAmount, 0)}
</div>
)}
</Box>
</Tooltip>
</Box>
<Modal
open={isOpen}
onClose={() => setIsOpen(false)}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<ModalBody
sx={{
width: "90%",
backgroundColor: "#A58700",
boxShadow: "none",
gap: "0px",
padding: "0px",
border: 0,
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "100%",
}}
>
<NewCrowdfundTitle>Super Like</NewCrowdfundTitle>
</Box>
<DialogContent sx={{ padding: "10px 12px" }}>
<Box>
<InputLabel
sx={{ color: "white" }}
htmlFor="standard-adornment-amount"
>
Amount
</InputLabel>
<BoundedNumericTextField
addIconButtons={!isScreenSmall}
minValue={+minPriceSuperDislike}
initialValue={minPriceSuperDislike.toString()}
maxValue={numberToInt(+currentBalance)}
allowDecimals={false}
allowNegatives={false}
id="standard-adornment-amount"
value={superDislikeDonationAmount}
afterChange={(e: string) => setSuperdislikeDonationAmount(+e)}
InputProps={{
style: {
fontSize: fontSizeMedium,
width: "80%",
border: `1px solid ${theme.palette.primary.main}`,
},
startAdornment: (
<InputAdornment position="start">
<img
style={{
height: "40px",
width: "40px",
}}
src={qortImg}
alt={"Qort Icon"}
/>
</InputAdornment>
),
}}
/>
<Box sx={{ display: "flex", gap: "5px", alignItems: "center" }}>
<img
style={{
height: "25px",
width: "25px",
}}
src={qortImg}
alt={"Qort Icon"}
/>
Balance: {currentBalance}
</Box>
<Spacer height="25px" />
<CommentInput
id="standard-multiline-flexible"
label="Comment Here"
multiline
minRows={8}
maxRows={8}
variant="filled"
value={comment}
InputLabelProps={{
style: { fontSize: fontSizeMedium, color: "white" },
}}
inputProps={{
maxLength: 500,
style: { fontSize: fontSizeLarge },
}}
onChange={e => setComment(e.target.value)}
sx={{ border: `1px solid ${theme.palette.primary.main}` }}
/>
</Box>
</DialogContent>
<CrowdfundActionButtonRow>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
margin: "10px",
width: "100%",
}}
>
<CrowdfundActionButton
onClick={() => {
setIsOpen(false);
resetValues();
onClose();
}}
variant="contained"
color="error"
>
Cancel
</CrowdfundActionButton>
<CrowdfundActionButton
variant="contained"
onClick={() => {
publishSuperDislike();
}}
>
Publish
</CrowdfundActionButton>
</Box>
</CrowdfundActionButtonRow>
</ModalBody>
</Modal>
{isOpenMultiplePublish && (
<MultiplePublish
isOpen={isOpenMultiplePublish}
onError={messageNotification => {
setIsOpenMultiplePublish(false);
setPublishes(null);
if (messageNotification) {
dispatch(
setNotification({
msg: messageNotification,
alertType: "error",
})
);
}
}}
onSubmit={() => {
onSuccess({
name: username,
message: comment,
service,
identifier,
amount: +superDislikeDonationAmount,
created: Date.now(),
});
setIsOpenMultiplePublish(false);
onClose();
dispatch(
setNotification({
msg: "Super like published",
alertType: "success",
})
);
}}
publishes={publishes}
/>
)}
</>
);
};

View File

@ -21,7 +21,7 @@ import {
import {
fontSizeLarge,
fontSizeMedium,
minPriceSuperlike,
minPriceSuperLike,
} from "../../../constants/Misc.ts";
import { setNotification } from "../../../state/features/notificationsSlice.ts";
import { RootState } from "../../../state/store.ts";
@ -52,7 +52,7 @@ export const SuperLike = ({
const [isOpen, setIsOpen] = useState<boolean>(false);
const [superlikeDonationAmount, setSuperlikeDonationAmount] =
useState<number>(minPriceSuperlike);
useState<number>(minPriceSuperLike);
const [currentBalance, setCurrentBalance] = useState<string>("");
const [comment, setComment] = useState<string>("");
@ -95,10 +95,10 @@ export const SuperLike = ({
throw new Error("Could not retrieve content creator's address");
if (
!superlikeDonationAmount ||
superlikeDonationAmount < minPriceSuperlike
superlikeDonationAmount < minPriceSuperLike
)
throw new Error(
`The amount is ${superlikeDonationAmount}, but it needs to be at least ${minPriceSuperlike} QORT`
`The amount is ${superlikeDonationAmount}, but it needs to be at least ${minPriceSuperLike} QORT`
);
const listOfPublishes = [];
@ -286,8 +286,8 @@ export const SuperLike = ({
</InputLabel>
<BoundedNumericTextField
addIconButtons={!isScreenSmall}
minValue={+minPriceSuperlike}
initialValue={minPriceSuperlike.toString()}
minValue={+minPriceSuperLike}
initialValue={minPriceSuperLike.toString()}
maxValue={numberToInt(+currentBalance)}
allowDecimals={false}
allowNegatives={false}

View File

@ -1,17 +1,20 @@
import * as React from "react";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import Divider from "@mui/material/Divider";
import ListItemText from "@mui/material/ListItemText";
import ListItemAvatar from "@mui/material/ListItemAvatar";
import Avatar from "@mui/material/Avatar";
import Typography from "@mui/material/Typography";
import EmojiEventsIcon from "@mui/icons-material/EmojiEvents";
import ThumbUpIcon from "@mui/icons-material/ThumbUp";
import { Box } from "@mui/material";
import Avatar from "@mui/material/Avatar";
import Divider from "@mui/material/Divider";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemAvatar from "@mui/material/ListItemAvatar";
import ListItemText from "@mui/material/ListItemText";
import Typography from "@mui/material/Typography";
import * as React from "react";
import { useSelector } from "react-redux";
import { RootState } from "../../../state/store";
import { useNavigate } from "react-router-dom";
import EmojiEventsIcon from "@mui/icons-material/EmojiEvents";
import { fontSizeSmall } from "../../../constants/Misc.ts";
import { RootState } from "../../../state/store";
import { formatDate } from "../../../utils/time.ts";
const truncateMessage = message => {
return message.length > 40 ? message.slice(0, 40) + "..." : message;
};
@ -139,6 +142,9 @@ export default function ListSuperLikes({ superlikes }) {
{forName}
</Box>
)}
<span style={{ fontSize: fontSizeSmall }}>
{formatDate(superlike.created)}
</span>
</Box>
</ListItem>
<Box

View File

@ -33,7 +33,7 @@ import {
FOR_SUPER_LIKE,
SUPER_LIKE_BASE,
} from "../../../constants/Identifiers.ts";
import { minPriceSuperlike } from "../../../constants/Misc.ts";
import { minPriceSuperLike } from "../../../constants/Misc.ts";
const generalLocal = localForage.createInstance({
name: "q-tube-general",
@ -141,7 +141,7 @@ export const Notifications = () => {
if (!result) continue;
const res = await getPaymentInfo(result);
if (
+res?.amount >= minPriceSuperlike &&
+res?.amount >= minPriceSuperLike &&
res.recipient === usernameAddress &&
isTimestampWithinRange(res?.timestamp, comment.created)
) {

View File

@ -1,4 +1,4 @@
const useTestIdentifiers = false;
export const useTestIdentifiers = false;
export const QTUBE_VIDEO_BASE = useTestIdentifiers
? "MYTEST_vid_"
: "qtube_vid_";

View File

@ -1,4 +1,7 @@
export const minPriceSuperlike = 1;
import { useTestIdentifiers } from "./Identifiers.ts";
export const minPriceSuperLike = 1;
export const minPriceSuperDislike = 1;
export const titleFormatter = /[\r\n]+/g;
export const titleFormatterOnSave = /[\r\n/<>:"'\\*|?]+/g;

View File

@ -4,7 +4,7 @@ import {
SUPER_LIKE_BASE,
} from "../../../constants/Identifiers.ts";
import {
minPriceSuperlike,
minPriceSuperLike,
titleFormatterOnSave,
} from "../../../constants/Misc.ts";
import { useFetchSuperLikes } from "../../../hooks/useFetchSuperLikes.tsx";
@ -199,7 +199,7 @@ export const useVideoContentState = () => {
if (!result) continue;
const res = await getPaymentInfo(result);
if (
+res?.amount >= minPriceSuperlike &&
+res?.amount >= minPriceSuperLike &&
res.recipient === nameAddressParam &&
isTimestampWithinRange(res?.timestamp, comment.created)
) {

View File

@ -28,7 +28,7 @@ import { EditPlaylist } from "../components/Publish/EditPlaylist/EditPlaylist";
import ConsentModal from "../components/common/ConsentModal";
import { useFetchSuperLikes } from "../hooks/useFetchSuperLikes";
import { SUPER_LIKE_BASE } from "../constants/Identifiers.ts";
import { minPriceSuperlike } from "../constants/Misc.ts";
import { minPriceSuperLike } from "../constants/Misc.ts";
interface Props {
children: React.ReactNode;
@ -167,7 +167,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
if (!result) continue;
const res = await getPaymentInfo(result);
if (
+res?.amount >= minPriceSuperlike &&
+res?.amount >= minPriceSuperLike &&
isTimestampWithinRange(res?.timestamp, comment.created)
) {
addSuperlikeRawDataGetToList({