mirror of
https://github.com/Qortal/q-support.git
synced 2025-02-11 17:55:50 +00:00
FeeHistoryModal is now editable by app owner
Fixed bug with feeFilter's Time Check in FeePricePublish.ts feeAmount in FeeData.tsx now fetches the newest price of a given FeeType instead of using a hardcoded value Fixed bug that prevented app from working if no FeeData was found Reverted publishing content as file back to base64
This commit is contained in:
parent
9434eef241
commit
75beecd26f
@ -15,10 +15,7 @@ import ShortUniqueId from "short-unique-id";
|
||||
import { allCategoryData } from "../../constants/Categories/Categories.ts";
|
||||
import { QSUPPORT_FILE_BASE } from "../../constants/Identifiers.ts";
|
||||
import { log, titleFormatter } from "../../constants/Misc.ts";
|
||||
import {
|
||||
feeAmountBase,
|
||||
supportedCoins,
|
||||
} from "../../constants/PublishFees/FeeData.tsx";
|
||||
import { supportedCoins } from "../../constants/PublishFees/FeeData.tsx";
|
||||
import { CoinType } from "../../constants/PublishFees/FeePricePublish/FeePricePublish.ts";
|
||||
import { payPublishFeeQORT } from "../../constants/PublishFees/SendFeeFunctions.ts";
|
||||
import { verifyPayment } from "../../constants/PublishFees/VerifyPayment.ts";
|
||||
@ -332,7 +329,7 @@ export const EditIssue = () => {
|
||||
bountyData,
|
||||
};
|
||||
if (payFee) {
|
||||
const publishFeeResponse = await payPublishFeeQORT(feeAmountBase);
|
||||
const publishFeeResponse = await payPublishFeeQORT("default", "QORT");
|
||||
if (!publishFeeResponse) {
|
||||
dispatch(
|
||||
setNotification({
|
||||
@ -365,7 +362,7 @@ export const EditIssue = () => {
|
||||
action: "PUBLISH_QDN_RESOURCE",
|
||||
name: name,
|
||||
service: "DOCUMENT",
|
||||
file: objectToFile(issueObject),
|
||||
data64: await objectToBase64(issueObject),
|
||||
title: title.slice(0, 50),
|
||||
description: metadescription,
|
||||
identifier: editIssueProperties.id,
|
||||
|
@ -326,7 +326,7 @@ export const EditPlaylist = () => {
|
||||
action: "PUBLISH_QDN_RESOURCE",
|
||||
name: username,
|
||||
service: "PLAYLIST",
|
||||
file: objectToFile(playlistObject),
|
||||
data64: await objectToBase64(playlistObject),
|
||||
title: title.slice(0, 50),
|
||||
description: metadescription,
|
||||
identifier: identifier,
|
||||
|
@ -21,8 +21,9 @@ import {
|
||||
titleFormatter,
|
||||
} from "../../constants/Misc.ts";
|
||||
import {
|
||||
feeAmountBase,
|
||||
feeDisclaimer,
|
||||
appName,
|
||||
feeDataDefault,
|
||||
feePriceToString,
|
||||
supportedCoins,
|
||||
} from "../../constants/PublishFees/FeeData.tsx";
|
||||
import { CoinType } from "../../constants/PublishFees/FeePricePublish/FeePricePublish.ts";
|
||||
@ -36,6 +37,7 @@ import { setNotification } from "../../state/features/notificationsSlice";
|
||||
import { RootState } from "../../state/store";
|
||||
import { BountyData, validateBountyInput } from "../../utils/qortalRequests.ts";
|
||||
import { objectToBase64, objectToFile } from "../../utils/PublishFormatter.ts";
|
||||
import { StateCheckBox } from "../../utils/StateCheckBox.tsx";
|
||||
import { isNumber } from "../../utils/utilFunctions.ts";
|
||||
import {
|
||||
AutocompleteQappNames,
|
||||
@ -116,7 +118,7 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
const [sourceCode, setSourceCode] = useState<string>("");
|
||||
const [coin, setCoin] = useState<CoinType>("QORT");
|
||||
const [showCoins, setShowCoins] = useState<boolean>(false);
|
||||
|
||||
const [payFee, setPayFee] = useState<boolean>(true);
|
||||
const categoryListRef = useRef<CategoryListRef>(null);
|
||||
const imagePublisherRef = useRef<ImagePublisherRef>(null);
|
||||
const autocompleteRef = useRef<QappNamesRef>(null);
|
||||
@ -283,12 +285,14 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
|
||||
const selectedQappName = autocompleteRef?.current?.getSelectedValue();
|
||||
|
||||
const publishFeeResponse = await payPublishFeeQORT(feeAmountBase);
|
||||
if (log) console.log("feeResponse: ", publishFeeResponse);
|
||||
let publishFeeResponse = undefined;
|
||||
|
||||
const feeData: PublishFeeData = {
|
||||
if (payFee) {
|
||||
publishFeeResponse = await payPublishFeeQORT("default", "QORT");
|
||||
if (log) console.log("feeResponse: ", publishFeeResponse);
|
||||
}
|
||||
const feeData = {
|
||||
signature: publishFeeResponse,
|
||||
senderName: "",
|
||||
};
|
||||
|
||||
const isBountyNumber = isNumber(bounty);
|
||||
@ -311,7 +315,6 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
feeData,
|
||||
bountyData,
|
||||
};
|
||||
|
||||
const QappNameString = autocompleteRef?.current?.getQappNameFetchString();
|
||||
const categoryString =
|
||||
categoryListRef.current?.getCategoriesFetchString(categoryList);
|
||||
@ -330,13 +333,15 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
action: "PUBLISH_QDN_RESOURCE",
|
||||
name: name,
|
||||
service: "DOCUMENT",
|
||||
file: objectToFile(issueObject),
|
||||
data64: await objectToBase64(issueObject),
|
||||
title: title.slice(0, 50),
|
||||
description: metadescription,
|
||||
identifier: identifier + "_metadata",
|
||||
tag1: QSUPPORT_FILE_BASE,
|
||||
filename: `video_metadata.json`,
|
||||
};
|
||||
console.log("issue object: ", issueObject);
|
||||
console.log("request body: ", requestBodyJson);
|
||||
listOfPublishes.push(requestBodyJson);
|
||||
|
||||
const multiplePublish = {
|
||||
@ -559,6 +564,11 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
</>
|
||||
</>
|
||||
)}
|
||||
<StateCheckBox
|
||||
label={`I agree to pay a fee of ${feePriceToString(feeDataDefault)} for further development of ${appName}`}
|
||||
defaultChecked
|
||||
onChange={b => setPayFee(b)}
|
||||
/>
|
||||
<ActionButtonRow>
|
||||
<ActionButton
|
||||
onClick={() => {
|
||||
@ -593,7 +603,6 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
</ThemeButtonBright>
|
||||
</Box>
|
||||
</ActionButtonRow>
|
||||
{feeDisclaimer}
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
|
||||
|
@ -11,3 +11,5 @@ export const QSUPPORT_PLAYLIST_BASE = useTestIdentifiers
|
||||
export const QSUPPORT_COMMENT_BASE = useTestIdentifiers
|
||||
? "qcomment_v1_MYTEST_support_"
|
||||
: "qcomment_v1_q_support_";
|
||||
|
||||
// have fee payment as checkbox
|
||||
|
@ -1,14 +1,23 @@
|
||||
import { CheckBox } from "@mui/icons-material";
|
||||
import { Box } from "@mui/material";
|
||||
import React from "react";
|
||||
import { StateCheckBox } from "../../utils/StateCheckBox.tsx";
|
||||
import { useTestIdentifiers } from "../Identifiers.ts";
|
||||
import {
|
||||
FeePrice,
|
||||
fetchCurrentPriceData,
|
||||
} from "./FeePricePublish/FeePricePublish.ts";
|
||||
|
||||
export const appName = "Q-Support";
|
||||
export const feeDestinationName = "Q-Support";
|
||||
|
||||
export const feeAmountBase = useTestIdentifiers ? 0.000001 : 0.25;
|
||||
export const FEE_BASE = useTestIdentifiers
|
||||
? "MYTEST_support_fees"
|
||||
: "q_support_fees";
|
||||
export const feeDataDefault = await fetchCurrentPriceData("default", "QORT");
|
||||
export const feePriceToString = (feePrice: FeePrice) => {
|
||||
return `${feePrice?.feeAmount} ${feePrice?.coinType}`;
|
||||
};
|
||||
|
||||
export const supportedCoins = [
|
||||
"QORT",
|
||||
@ -24,17 +33,26 @@ export const supportedCoins = [
|
||||
export const maxFeePublishTimeDiff = 10; // time in minutes before/after publish when fee is considered valid
|
||||
export type FeeType = "default" | "comment" | "like" | "dislike" | "superlike";
|
||||
|
||||
export const feeDisclaimerString = `When Publishing (but not editing) Issues ${feeAmountBase} \n
|
||||
QORT is requested to fund continued development of Q-Support.`;
|
||||
|
||||
export const feeDisclaimer = (
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "28px",
|
||||
color: "#f44336",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{feeDisclaimerString}
|
||||
</Box>
|
||||
// use wherever the fee is communicated to the user
|
||||
const feeCheckBox = (
|
||||
<StateCheckBox
|
||||
label={`I agree to pay a fee of ${feePriceToString(feeDataDefault)} to support further development of ${appName}`}
|
||||
defaultChecked
|
||||
/>
|
||||
);
|
||||
|
||||
// // Test senderName removal on Publish
|
||||
// export const feeDisclaimerString = `When Publishing (but not editing) Issues ${feeAmountBase} \n
|
||||
// QORT is requested to fund continued development of Q-Support.`;
|
||||
//
|
||||
// export const feeDisclaimer = (
|
||||
// <Box
|
||||
// sx={{
|
||||
// fontSize: "28px",
|
||||
// color: "#f44336",
|
||||
// fontWeight: 600,
|
||||
// }}
|
||||
// >
|
||||
// {feeDisclaimerString}
|
||||
// </Box>
|
||||
// );
|
||||
|
259
src/constants/PublishFees/FeePricePublish/DataEditor.tsx
Normal file
259
src/constants/PublishFees/FeePricePublish/DataEditor.tsx
Normal file
@ -0,0 +1,259 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
MenuItem,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { key } from "localforage";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { s } from "vite/dist/node/types.d-aGj9QkWt";
|
||||
import { ThemeButton } from "../../../pages/Home/Home-styles.tsx";
|
||||
import { setNotification } from "../../../state/features/notificationsSlice.ts";
|
||||
import BoundedNumericTextField from "../../../utils/BoundedNumericTextField.tsx";
|
||||
import {
|
||||
objectToBase64,
|
||||
objectToFile,
|
||||
} from "../../../utils/PublishFormatter.ts";
|
||||
import StateTextField from "../../../utils/StateTextField.tsx";
|
||||
import { objectIndexToKey } from "../../../utils/utilFunctions.ts";
|
||||
import { appName, FEE_BASE, supportedCoins } from "../FeeData.tsx";
|
||||
import { DataTableProps } from "./DataTable.tsx";
|
||||
import { CoinType, FeePrice, feesPublishService } from "./FeePricePublish.ts";
|
||||
|
||||
export const DataEditor = ({ columnNames, data, sx }: DataTableProps) => {
|
||||
const [editedData, setEditedData] = useState<FeePrice[]>(data);
|
||||
|
||||
const addRow = () => {
|
||||
setEditedData(editedData => [
|
||||
...editedData,
|
||||
{
|
||||
time: undefined,
|
||||
feeAmount: undefined,
|
||||
feeType: undefined,
|
||||
coinType: undefined,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const removeRow = () => {
|
||||
setEditedData(editedData => editedData.slice(0, -1));
|
||||
};
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const updateData = (
|
||||
value: string | number,
|
||||
rowIndex: number,
|
||||
cellIndex: number
|
||||
) => {
|
||||
const rowData = editedData[rowIndex];
|
||||
|
||||
const key = objectIndexToKey(rowData, cellIndex);
|
||||
|
||||
const newData: FeePrice[] = editedData.map((row, rIndex) => {
|
||||
return rIndex === rowIndex
|
||||
? { ...row, [key]: value || undefined }
|
||||
: { ...row };
|
||||
});
|
||||
setEditedData(newData);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log("editedData is: ", editedData);
|
||||
}, [editedData]);
|
||||
|
||||
const getCellForm = (rowIndex: number, cellIndex: number) => {
|
||||
const rowData = editedData[rowIndex];
|
||||
|
||||
const key = objectIndexToKey(rowData, cellIndex);
|
||||
const value = rowData[key]?.toString();
|
||||
|
||||
const feeAmount = (
|
||||
<BoundedNumericTextField
|
||||
variant={"standard"}
|
||||
value={value}
|
||||
initialValue={value || ""}
|
||||
addIconButtons={false}
|
||||
sx={{ width: "60%" }}
|
||||
onBlur={s => {
|
||||
updateData(+s, rowIndex, cellIndex);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const feeType = (
|
||||
<StateTextField
|
||||
variant={"standard"}
|
||||
value={value}
|
||||
initialValue={value || ""}
|
||||
sx={{ width: "55%" }}
|
||||
onBlur={e => {
|
||||
updateData(e.target.value, rowIndex, cellIndex);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const coinTypeAC = (
|
||||
<StateTextField
|
||||
variant={"outlined"}
|
||||
select
|
||||
fullWidth
|
||||
value={value}
|
||||
initialValue={value || undefined}
|
||||
onBlur={e =>
|
||||
updateData(e.target.value as CoinType, rowIndex, cellIndex)
|
||||
}
|
||||
sx={{
|
||||
width: "100%",
|
||||
}}
|
||||
options={supportedCoins}
|
||||
></StateTextField>
|
||||
);
|
||||
switch (cellIndex) {
|
||||
case 0:
|
||||
return value ? new Date(+value).toDateString() : "";
|
||||
case 1:
|
||||
return feeAmount;
|
||||
case 2:
|
||||
return feeType;
|
||||
case 3:
|
||||
return coinTypeAC;
|
||||
}
|
||||
};
|
||||
|
||||
const isValidPublish = (publishData: FeePrice[]) => {
|
||||
return publishData.every(value => {
|
||||
return Object.values(value).every(value => !!value);
|
||||
});
|
||||
};
|
||||
|
||||
const publish = async (feeData: FeePrice[]) => {
|
||||
qortalRequest({
|
||||
action: "PUBLISH_QDN_RESOURCE",
|
||||
name: appName,
|
||||
identifier: FEE_BASE,
|
||||
service: feesPublishService,
|
||||
data64: await objectToBase64(feeData),
|
||||
}).then(response => {
|
||||
dispatch(
|
||||
setNotification({
|
||||
msg: "Issue published",
|
||||
alertType: "success",
|
||||
})
|
||||
);
|
||||
setEditedData(feeData);
|
||||
});
|
||||
};
|
||||
const publishFeeData = () => {
|
||||
const dataLength = editedData.length - 1;
|
||||
|
||||
const dateEmpty = !editedData[dataLength]?.time;
|
||||
const dataWithDate: FeePrice[] = editedData.map((row, rIndex) => {
|
||||
return rIndex === dataLength && dateEmpty
|
||||
? { ...row, time: Date.now() }
|
||||
: { ...row };
|
||||
});
|
||||
|
||||
if (isValidPublish(dataWithDate)) publish(dataWithDate);
|
||||
else {
|
||||
const notificationObj = {
|
||||
msg: "Publish Fee Data is not Valid",
|
||||
alertType: "error",
|
||||
};
|
||||
dispatch(setNotification(notificationObj));
|
||||
}
|
||||
};
|
||||
|
||||
const boldSX = {
|
||||
fontSize: "30px",
|
||||
textAlign: "center",
|
||||
fontWeight: "bold",
|
||||
};
|
||||
const cellSX = {
|
||||
fontSize: "25px",
|
||||
fontWeight: "normal",
|
||||
textAlign: "center",
|
||||
};
|
||||
|
||||
const AddRemoveRowButtonSX = {
|
||||
color: "text.primary",
|
||||
fontSize: "20px",
|
||||
fontWeight: "bold",
|
||||
width: "30%",
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableContainer sx={{ width: "100%", ...sx }}>
|
||||
<Table align="center" stickyHeader>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{columnNames.map((columnName, index) => (
|
||||
<TableCell sx={boldSX} key={columnName + index}>
|
||||
{columnName}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{editedData.map((tableRow, rowIndex) => {
|
||||
return (
|
||||
<TableRow key={tableRow.toString() + rowIndex}>
|
||||
{<TableCell sx={boldSX}>{rowIndex + 1}</TableCell>}
|
||||
|
||||
{Object.values(tableRow).map((tableCell, cellIndex) => (
|
||||
<TableCell sx={cellSX} key={rowIndex + cellIndex}>
|
||||
{getCellForm(rowIndex, cellIndex)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
gap: "5%",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
sx={AddRemoveRowButtonSX}
|
||||
variant={"contained"}
|
||||
color={"success"}
|
||||
onClick={addRow}
|
||||
>
|
||||
Add Row
|
||||
</Button>
|
||||
<Button
|
||||
sx={AddRemoveRowButtonSX}
|
||||
variant={"contained"}
|
||||
color={"error"}
|
||||
onClick={removeRow}
|
||||
>
|
||||
Remove Row
|
||||
</Button>
|
||||
</Box>
|
||||
<ThemeButton
|
||||
sx={{
|
||||
fontSize: "20px",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
onClick={publishFeeData}
|
||||
>
|
||||
Publish
|
||||
</ThemeButton>
|
||||
</>
|
||||
);
|
||||
};
|
@ -8,46 +8,51 @@ import {
|
||||
} from "@mui/material";
|
||||
import React from "react";
|
||||
import { SxProps } from "@mui/material/styles";
|
||||
import { FeePrice } from "./FeePricePublish.ts";
|
||||
|
||||
export interface DataTableProps {
|
||||
columnNames: string[];
|
||||
data: string[][];
|
||||
data: FeePrice[];
|
||||
sx?: SxProps;
|
||||
}
|
||||
|
||||
export const DataTable = ({ columnNames, data, sx }: DataTableProps) => {
|
||||
const boldSX = {
|
||||
fontSize: "30px",
|
||||
textAlign: "center",
|
||||
fontWeight: "bold",
|
||||
};
|
||||
const cellSX = {
|
||||
fontSize: "25px",
|
||||
fontWeight: "normal",
|
||||
textAlign: "center",
|
||||
};
|
||||
|
||||
const formatCell = (s: string, index: number) => {
|
||||
if (index === 0) return new Date(s).toDateString();
|
||||
else return s;
|
||||
};
|
||||
return (
|
||||
<TableContainer sx={{ ...sx }}>
|
||||
<Table align="center" stickyHeader>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{columnNames.map((columnName, index) => (
|
||||
<TableCell
|
||||
sx={{
|
||||
fontSize: "30px",
|
||||
textAlign: "center",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
key={columnName + index}
|
||||
>
|
||||
<TableCell sx={boldSX} key={columnName + index}>
|
||||
{columnName}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data.map((tableRow, index) => {
|
||||
{data.map((tableRow, rowIndex) => {
|
||||
return (
|
||||
<TableRow key={tableRow.toString() + index}>
|
||||
{tableRow.map((tableCell, index) => (
|
||||
<TableCell
|
||||
sx={{
|
||||
fontSize: index === 0 ? "30px" : "25px",
|
||||
fontWeight: index === 0 ? "bold" : "normal",
|
||||
textAlign: "center",
|
||||
}}
|
||||
key={tableCell + index}
|
||||
>
|
||||
{tableCell}
|
||||
<TableRow key={tableRow.toString() + rowIndex}>
|
||||
{<TableCell sx={boldSX}>{rowIndex + 1}</TableCell>}
|
||||
|
||||
{Object.values(tableRow).map((tableCell, cellIndex) => (
|
||||
<TableCell sx={cellSX} key={tableCell + cellIndex}>
|
||||
{formatCell(tableCell, cellIndex)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
|
@ -4,21 +4,19 @@ import { appName } from "../FeeData.tsx";
|
||||
import { ModalBody } from "./FeePricePublish-styles.tsx";
|
||||
import { useEffect, useState } from "react";
|
||||
import { userHasName } from "../VerifyPayment-Functions.ts";
|
||||
import { FeeHistoryTable } from "./FeeHistoryTable.tsx";
|
||||
import { FeeHistoryModalBody } from "./FeeHistoryModalBody.tsx";
|
||||
|
||||
export const FeeHistoryModal = () => {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [userOwnsApp, setUserOwnsApp] = useState<boolean>(false);
|
||||
|
||||
const theme = useTheme();
|
||||
useEffect(() => {
|
||||
userHasName(appName).then(userHasName => setUserOwnsApp(userHasName));
|
||||
}, []);
|
||||
|
||||
const buttonSX = {
|
||||
fontSize: "20px",
|
||||
color: theme.palette.secondary.main,
|
||||
fontWeight: "bold",
|
||||
};
|
||||
|
||||
if (theme.palette.mode === "light")
|
||||
buttonSX["&:hover"] = { backgroundColor: theme.palette.primary.dark };
|
||||
|
||||
@ -37,7 +35,7 @@ export const FeeHistoryModal = () => {
|
||||
onClose={() => setOpen(false)}
|
||||
>
|
||||
<ModalBody sx={{ width: "75vw", maxWidth: "75vw" }}>
|
||||
<FeeHistoryTable />
|
||||
<FeeHistoryModalBody />
|
||||
<Button sx={buttonSX} onClick={() => setOpen(false)}>
|
||||
Close
|
||||
</Button>
|
||||
|
@ -0,0 +1,38 @@
|
||||
import { appName } from "../FeeData.tsx";
|
||||
import { userHasName } from "../VerifyPayment-Functions.ts";
|
||||
import { DataEditor } from "./DataEditor.tsx";
|
||||
import { DataTable } from "./DataTable.tsx";
|
||||
import { FeePrice, fetchFees } from "./FeePricePublish.ts";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
export interface FeeHistoryModalBodyProps {
|
||||
showFeeType?: boolean;
|
||||
showCoinType?: boolean;
|
||||
filterData?: () => string[][];
|
||||
}
|
||||
export const FeeHistoryModalBody = ({
|
||||
showFeeType = true,
|
||||
showCoinType = true,
|
||||
}: FeeHistoryModalBodyProps) => {
|
||||
const [feeData, setFeeData] = useState<FeePrice[]>([]);
|
||||
const [userOwnsApp, setUserOwnsApp] = useState<boolean>(false);
|
||||
|
||||
const fetchFeesOnStartup = () => {
|
||||
fetchFees().then(feeResponse => {
|
||||
setFeeData(feeResponse);
|
||||
});
|
||||
userHasName(appName).then(userHasName => setUserOwnsApp(userHasName));
|
||||
};
|
||||
|
||||
useEffect(fetchFeesOnStartup, []);
|
||||
|
||||
const columnNames = ["ID", "Date", "Fee Amount"];
|
||||
if (showFeeType) columnNames.push("Fee Type");
|
||||
if (showCoinType) columnNames.push("Coin Type");
|
||||
|
||||
return userOwnsApp ? (
|
||||
<DataEditor columnNames={columnNames} data={feeData} />
|
||||
) : (
|
||||
<DataTable columnNames={columnNames} data={feeData} />
|
||||
);
|
||||
};
|
@ -1,49 +0,0 @@
|
||||
import { DataTable } from "./DataTable.tsx";
|
||||
import { FeePrice, fetchFees } from "./FeePricePublish.ts";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
export interface FeeHistoryProps {
|
||||
showFeeType?: boolean;
|
||||
showCoinType?: boolean;
|
||||
filterData?: () => string[][];
|
||||
}
|
||||
export const FeeHistoryTable = ({
|
||||
showFeeType = true,
|
||||
showCoinType = true,
|
||||
filterData,
|
||||
}: FeeHistoryProps) => {
|
||||
const [feeData, setFeeData] = useState<FeePrice[]>([]);
|
||||
|
||||
const fetchFeesOnStartup = () => {
|
||||
fetchFees().then(feeResponse => {
|
||||
setFeeData(filterData ? feeData.filter(filterData) : feeResponse);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(fetchFeesOnStartup, []);
|
||||
|
||||
const columnNames = ["ID", "Date", "Fee Amount"];
|
||||
if (showFeeType) columnNames.push("Fee Type");
|
||||
if (showCoinType) columnNames.push("Coin Type");
|
||||
|
||||
const data: string[][] = [];
|
||||
|
||||
const getRowData = (row: FeePrice, index: number) => {
|
||||
const rowData: string[] = [];
|
||||
rowData.push(
|
||||
index.toString(),
|
||||
new Date(row.time).toDateString(),
|
||||
row.feeAmount.toString()
|
||||
);
|
||||
|
||||
if (showFeeType) rowData.push(row.feeType);
|
||||
if (showCoinType) rowData.push(row.coinType);
|
||||
|
||||
return rowData;
|
||||
};
|
||||
|
||||
feeData.map((row, index) => {
|
||||
data.push(getRowData(row, index + 1));
|
||||
});
|
||||
return <DataTable columnNames={columnNames} data={data} />;
|
||||
};
|
@ -1,11 +1,7 @@
|
||||
import { setFeeData } from "../../../state/features/globalSlice.ts";
|
||||
import { store } from "../../../state/store.js";
|
||||
import {
|
||||
objectToBase64,
|
||||
objectToFile,
|
||||
} from "../../../utils/PublishFormatter.ts";
|
||||
import { useTestIdentifiers } from "../../Identifiers.ts";
|
||||
import { appName, FEE_BASE, feeAmountBase, FeeType } from "../FeeData.tsx";
|
||||
import { objectToFile } from "../../../utils/PublishFormatter.ts";
|
||||
import { appName, FEE_BASE, FeeType } from "../FeeData.tsx";
|
||||
|
||||
export type CoinType = "QORT" | "BTC" | "LTC" | "DOGE" | "DGB" | "RVN" | "ARRR";
|
||||
|
||||
@ -16,7 +12,7 @@ export interface FeePrice {
|
||||
coinType: CoinType;
|
||||
}
|
||||
|
||||
const feesPublishService = "DOCUMENT";
|
||||
export const feesPublishService = "DOCUMENT";
|
||||
|
||||
export const fetchFees = async () => {
|
||||
const feeData = store.getState().global.feeData;
|
||||
@ -48,45 +44,39 @@ export const fetchFeesRedux = () => {
|
||||
fetchFees().then(feeData => store.dispatch(setFeeData(feeData)));
|
||||
};
|
||||
|
||||
export const addFeePrice = async (
|
||||
feeAmount = feeAmountBase,
|
||||
export const fetchCurrentPriceData = async (
|
||||
feeType: FeeType = "default",
|
||||
coinType: CoinType = "QORT"
|
||||
) => {
|
||||
const fees = await fetchFees();
|
||||
if (fees?.length === 0 || !fees) return undefined;
|
||||
|
||||
fees.push({
|
||||
time: Date.now(),
|
||||
feeAmount,
|
||||
feeType,
|
||||
coinType,
|
||||
});
|
||||
const filteredFees = fees.filter(
|
||||
price => price.feeType === feeType && price.coinType === coinType
|
||||
);
|
||||
|
||||
console.log("fees are: ", fees);
|
||||
await qortalRequest({
|
||||
action: "PUBLISH_QDN_RESOURCE",
|
||||
name: appName,
|
||||
identifier: FEE_BASE,
|
||||
service: feesPublishService,
|
||||
file: objectToFile(fees),
|
||||
});
|
||||
return filteredFees.at(-1) as FeePrice;
|
||||
};
|
||||
|
||||
const feeFilter = (fee: FeePrice, feeToVerify: FeePrice) => {
|
||||
const nameCheck = fee.feeType === feeToVerify.feeType;
|
||||
const coinTypeCheck = fee.coinType === feeToVerify.coinType;
|
||||
const timeCheck = feeToVerify.time <= feeToVerify.time;
|
||||
const timeCheck = fee.time <= feeToVerify.time;
|
||||
const filter = nameCheck && coinTypeCheck && timeCheck;
|
||||
|
||||
return nameCheck && coinTypeCheck && timeCheck;
|
||||
return filter;
|
||||
};
|
||||
|
||||
export const verifyFeeAmount = async (feeToVerify: FeePrice) => {
|
||||
if (useTestIdentifiers) return true;
|
||||
|
||||
const fees = await fetchFees();
|
||||
const filteredFees = fees.filter(fee => feeFilter(fee, feeToVerify));
|
||||
if (filteredFees.length === 0) return false;
|
||||
if (filteredFees.length === 0) {
|
||||
console.log("no filtered fees");
|
||||
return false;
|
||||
}
|
||||
// gets fee that applies at the time of feeToVerify
|
||||
const feeToCheck = filteredFees.at(-1);
|
||||
const isFeeAmountValid = feeToVerify.feeAmount >= feeToCheck.feeAmount;
|
||||
|
||||
const feeToCheck = filteredFees[filteredFees.length - 1]; // gets fee that applies at the time of feeToVerify
|
||||
return feeToVerify.feeAmount >= feeToCheck.feeAmount;
|
||||
return isFeeAmountValid;
|
||||
};
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { feeDestinationName, FeeType } from "./FeeData.tsx";
|
||||
import { CoinType } from "./FeePricePublish/FeePricePublish.ts";
|
||||
import {
|
||||
CoinType,
|
||||
fetchCurrentPriceData,
|
||||
} from "./FeePricePublish/FeePricePublish.ts";
|
||||
|
||||
export interface NameData {
|
||||
name: string;
|
||||
@ -58,7 +61,7 @@ export const sendQORTtoName = async (name: string, amount: number) => {
|
||||
|
||||
export interface PublishFeeData {
|
||||
signature: string;
|
||||
senderName: string;
|
||||
senderName?: string;
|
||||
createdTimestamp?: number; //timestamp of the metadata publish, NOT the send feeAmount publish, added after publish is fetched
|
||||
updatedTimestamp?: number;
|
||||
feeType?: FeeType;
|
||||
@ -73,7 +76,11 @@ export interface CommentObject {
|
||||
feeData: PublishFeeData;
|
||||
}
|
||||
|
||||
export const payPublishFeeQORT = async (feeAmount: number) => {
|
||||
const publish = await sendQORTtoName(feeDestinationName, feeAmount);
|
||||
export const payPublishFeeQORT = async (
|
||||
feeType: FeeType = "default",
|
||||
coinType: CoinType = "QORT"
|
||||
) => {
|
||||
const feeData = await fetchCurrentPriceData(feeType, coinType);
|
||||
const publish = await sendQORTtoName(feeDestinationName, feeData.feeAmount);
|
||||
return publish?.signature;
|
||||
};
|
||||
|
@ -45,7 +45,7 @@ const verifySignature = async (feeData: PublishFeeData) => {
|
||||
});
|
||||
|
||||
const signatureTime = signatureData.timestamp;
|
||||
let doesTimeMatch: boolean = false;
|
||||
let doesTimeMatch = false;
|
||||
if (!updatedTimestamp) {
|
||||
const timeDiff = createdTimestamp - signatureTime;
|
||||
const timeDiffMinutes = Math.abs(timeDiff) / 1000 / 60;
|
||||
|
@ -10,19 +10,20 @@ import React, { useRef, useState } from "react";
|
||||
|
||||
type eventType = React.ChangeEvent<HTMLInputElement>;
|
||||
type BoundedNumericTextFieldProps = {
|
||||
minValue: number;
|
||||
maxValue: number;
|
||||
minValue?: number;
|
||||
maxValue?: number;
|
||||
addIconButtons?: boolean;
|
||||
allowDecimals?: boolean;
|
||||
allowNegatives?: boolean;
|
||||
onChange?: (s: string) => void;
|
||||
onBlur?: (s: string) => void;
|
||||
initialValue?: string;
|
||||
maxSigDigits?: number;
|
||||
} & TextFieldProps;
|
||||
|
||||
export const BoundedNumericTextField = ({
|
||||
minValue,
|
||||
maxValue,
|
||||
minValue = 0,
|
||||
maxValue = Number.MAX_VALUE,
|
||||
addIconButtons = true,
|
||||
allowDecimals = true,
|
||||
allowNegatives = false,
|
||||
@ -30,6 +31,8 @@ export const BoundedNumericTextField = ({
|
||||
maxSigDigits = 6,
|
||||
...props
|
||||
}: BoundedNumericTextFieldProps) => {
|
||||
const { onChange, onBlur, ...noChangeProps } = { ...props };
|
||||
|
||||
const [textFieldValue, setTextFieldValue] = useState<string>(
|
||||
initialValue || ""
|
||||
);
|
||||
@ -108,6 +111,7 @@ export const BoundedNumericTextField = ({
|
||||
let value = e.target.value;
|
||||
if (stringIsEmpty(value) || value === ".") {
|
||||
setTextFieldValue("");
|
||||
if (onBlur) onBlur("");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -116,9 +120,9 @@ export const BoundedNumericTextField = ({
|
||||
if (isAllZerosNum.test(value)) value = minValue.toString();
|
||||
|
||||
setTextFieldValue(value);
|
||||
if (onBlur) onBlur(value);
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { onChange, ...noChangeProps } = { ...props };
|
||||
|
||||
return (
|
||||
<TextField
|
||||
{...noChangeProps}
|
||||
|
45
src/utils/StateCheckBox.tsx
Normal file
45
src/utils/StateCheckBox.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import RemoveIcon from "@mui/icons-material/Remove";
|
||||
import { CheckboxProps, FormControlLabel, Checkbox } from "@mui/material";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { supportedCoins } from "../constants/PublishFees/FeeData.tsx";
|
||||
import StateTextField from "./StateTextField.tsx";
|
||||
|
||||
type eventType = React.ChangeEvent<HTMLInputElement>;
|
||||
|
||||
type StateCheckBoxProps = {
|
||||
onChange?: (b: boolean) => void;
|
||||
label?: string;
|
||||
} & CheckboxProps;
|
||||
|
||||
export const StateCheckBox = ({ ...props }: StateCheckBoxProps) => {
|
||||
const { onChange, defaultChecked, label, ...noChangeProps } = { ...props };
|
||||
|
||||
const [checkValue, setCheckValue] = useState<boolean>(defaultChecked);
|
||||
const ref = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const stringIsEmpty = (value: string) => {
|
||||
return value === "";
|
||||
};
|
||||
|
||||
const listeners = (e: eventType) => {
|
||||
const newValue = e.target.checked;
|
||||
setCheckValue(newValue);
|
||||
if (onChange) onChange(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControlLabel
|
||||
label={label}
|
||||
control={
|
||||
<Checkbox
|
||||
{...noChangeProps}
|
||||
onChange={e => listeners(e as eventType)}
|
||||
checked={checkValue}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default StateTextField;
|
64
src/utils/StateTextField.tsx
Normal file
64
src/utils/StateTextField.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import RemoveIcon from "@mui/icons-material/Remove";
|
||||
import {
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
TextField,
|
||||
TextFieldProps,
|
||||
} from "@mui/material";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { supportedCoins } from "../constants/PublishFees/FeeData.tsx";
|
||||
|
||||
type eventType = React.ChangeEvent<HTMLInputElement>;
|
||||
type StateTextFieldProps = {
|
||||
onChange?: (s: string) => void;
|
||||
initialValue?: string;
|
||||
options?: string[];
|
||||
} & TextFieldProps;
|
||||
|
||||
export const StateTextField = ({
|
||||
initialValue,
|
||||
options,
|
||||
...props
|
||||
}: StateTextFieldProps) => {
|
||||
const { onChange, ...noChangeProps } = { ...props };
|
||||
|
||||
const [textFieldValue, setTextFieldValue] = useState<string>(
|
||||
initialValue || ""
|
||||
);
|
||||
const ref = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const stringIsEmpty = (value: string) => {
|
||||
return value === "";
|
||||
};
|
||||
|
||||
const listeners = (e: eventType) => {
|
||||
const newValue = e.target.value;
|
||||
setTextFieldValue(newValue);
|
||||
if (onChange) onChange(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<TextField
|
||||
{...noChangeProps}
|
||||
InputProps={{
|
||||
...props?.InputProps,
|
||||
}}
|
||||
onChange={e => listeners(e as eventType)}
|
||||
autoComplete="off"
|
||||
value={textFieldValue}
|
||||
inputRef={ref}
|
||||
>
|
||||
{options &&
|
||||
props?.select &&
|
||||
options.map((option, index) => (
|
||||
<MenuItem value={option} key={option + index}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
);
|
||||
};
|
||||
|
||||
export default StateTextField;
|
@ -76,7 +76,7 @@ export const getCrowdfund = async (crowdfundLink: string) => {
|
||||
const splitLink = crowdfundLink.split("/");
|
||||
const name = splitLink[5];
|
||||
const identifier = splitLink[6];
|
||||
console.log("fetching crowdfund");
|
||||
|
||||
return await qortalRequest({
|
||||
action: "FETCH_QDN_RESOURCE",
|
||||
service: "DOCUMENT",
|
||||
|
@ -28,3 +28,12 @@ export const isNumber = (input: string) => {
|
||||
export const truncateNumber = (value: string | number, sigDigits: number) => {
|
||||
return Number(value).toFixed(sigDigits);
|
||||
};
|
||||
export const objectIndexToKey = (obj: object, index: number) => {
|
||||
const keys = Object.keys(obj);
|
||||
|
||||
if (index < 0 || index >= keys.length) {
|
||||
throw new Error(`Invalid index: ${index}`);
|
||||
}
|
||||
const key = keys[index];
|
||||
return key;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user