diff --git a/src/components/common/ContentButtons/SuperDislike.tsx b/src/components/common/ContentButtons/SuperDislike.tsx new file mode 100644 index 0000000..ae922a8 --- /dev/null +++ b/src/components/common/ContentButtons/SuperDislike.tsx @@ -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(false); + + const [superDislikeDonationAmount, setSuperdislikeDonationAmount] = + useState(minPriceSuperDislike); + const [currentBalance, setCurrentBalance] = useState(""); + + const [comment, setComment] = useState(""); + const username = useSelector((state: RootState) => state.auth?.user?.name); + const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false); + const [publishes, setPublishes] = useState(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 ( + <> + { + 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, + }} + > + + + + + {numberOfSuperdislikes === 0 ? null : ( +
+ + {numberOfSuperdislikes} + + {"Qort + {truncateNumber(totalAmount, 0)} +
+ )} +
+
+
+ setIsOpen(false)} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + + + Super Like + + + + + Amount + + setSuperdislikeDonationAmount(+e)} + InputProps={{ + style: { + fontSize: fontSizeMedium, + width: "80%", + border: `1px solid ${theme.palette.primary.main}`, + }, + startAdornment: ( + + {"Qort + + ), + }} + /> + + + {"Qort + Balance: {currentBalance} + + + + setComment(e.target.value)} + sx={{ border: `1px solid ${theme.palette.primary.main}` }} + /> + + + + + { + setIsOpen(false); + resetValues(); + onClose(); + }} + variant="contained" + color="error" + > + Cancel + + + { + publishSuperDislike(); + }} + > + Publish + + + + + + {isOpenMultiplePublish && ( + { + 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} + /> + )} + + ); +}; diff --git a/src/components/common/ContentButtons/SuperLike.tsx b/src/components/common/ContentButtons/SuperLike.tsx index 48e702c..8c5fed5 100644 --- a/src/components/common/ContentButtons/SuperLike.tsx +++ b/src/components/common/ContentButtons/SuperLike.tsx @@ -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(false); const [superlikeDonationAmount, setSuperlikeDonationAmount] = - useState(minPriceSuperlike); + useState(minPriceSuperLike); const [currentBalance, setCurrentBalance] = useState(""); const [comment, setComment] = useState(""); @@ -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 = ({ { return message.length > 40 ? message.slice(0, 40) + "..." : message; }; @@ -139,6 +142,9 @@ export default function ListSuperLikes({ superlikes }) { {forName} )} + + {formatDate(superlike.created)} + { if (!result) continue; const res = await getPaymentInfo(result); if ( - +res?.amount >= minPriceSuperlike && + +res?.amount >= minPriceSuperLike && res.recipient === usernameAddress && isTimestampWithinRange(res?.timestamp, comment.created) ) { diff --git a/src/constants/Identifiers.ts b/src/constants/Identifiers.ts index b31b257..432722c 100644 --- a/src/constants/Identifiers.ts +++ b/src/constants/Identifiers.ts @@ -1,4 +1,4 @@ -const useTestIdentifiers = false; +export const useTestIdentifiers = false; export const QTUBE_VIDEO_BASE = useTestIdentifiers ? "MYTEST_vid_" : "qtube_vid_"; diff --git a/src/constants/Misc.ts b/src/constants/Misc.ts index 189f673..31d1d69 100644 --- a/src/constants/Misc.ts +++ b/src/constants/Misc.ts @@ -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; diff --git a/src/pages/ContentPages/VideoContent/VideoContent-State.ts b/src/pages/ContentPages/VideoContent/VideoContent-State.ts index 0ed47d1..d456672 100644 --- a/src/pages/ContentPages/VideoContent/VideoContent-State.ts +++ b/src/pages/ContentPages/VideoContent/VideoContent-State.ts @@ -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) ) { diff --git a/src/wrappers/GlobalWrapper.tsx b/src/wrappers/GlobalWrapper.tsx index 58a95c9..53002e8 100644 --- a/src/wrappers/GlobalWrapper.tsx +++ b/src/wrappers/GlobalWrapper.tsx @@ -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 = ({ children, setTheme }) => { if (!result) continue; const res = await getPaymentInfo(result); if ( - +res?.amount >= minPriceSuperlike && + +res?.amount >= minPriceSuperLike && isTimestampWithinRange(res?.timestamp, comment.created) ) { addSuperlikeRawDataGetToList({