mirror of
https://github.com/Qortal/q-support.git
synced 2025-02-11 17:55:50 +00:00
Merge pull request #12 from QortalSeth/main
FeeHistoryModal is now editable by app owner
This commit is contained in:
commit
69aab3f604
@ -15,10 +15,7 @@ import ShortUniqueId from "short-unique-id";
|
|||||||
import { allCategoryData } from "../../constants/Categories/Categories.ts";
|
import { allCategoryData } from "../../constants/Categories/Categories.ts";
|
||||||
import { QSUPPORT_FILE_BASE } from "../../constants/Identifiers.ts";
|
import { QSUPPORT_FILE_BASE } from "../../constants/Identifiers.ts";
|
||||||
import { log, titleFormatter } from "../../constants/Misc.ts";
|
import { log, titleFormatter } from "../../constants/Misc.ts";
|
||||||
import {
|
import { supportedCoins } from "../../constants/PublishFees/FeeData.tsx";
|
||||||
feeAmountBase,
|
|
||||||
supportedCoins,
|
|
||||||
} from "../../constants/PublishFees/FeeData.tsx";
|
|
||||||
import { CoinType } from "../../constants/PublishFees/FeePricePublish/FeePricePublish.ts";
|
import { CoinType } from "../../constants/PublishFees/FeePricePublish/FeePricePublish.ts";
|
||||||
import { payPublishFeeQORT } from "../../constants/PublishFees/SendFeeFunctions.ts";
|
import { payPublishFeeQORT } from "../../constants/PublishFees/SendFeeFunctions.ts";
|
||||||
import { verifyPayment } from "../../constants/PublishFees/VerifyPayment.ts";
|
import { verifyPayment } from "../../constants/PublishFees/VerifyPayment.ts";
|
||||||
@ -332,7 +329,7 @@ export const EditIssue = () => {
|
|||||||
bountyData,
|
bountyData,
|
||||||
};
|
};
|
||||||
if (payFee) {
|
if (payFee) {
|
||||||
const publishFeeResponse = await payPublishFeeQORT(feeAmountBase);
|
const publishFeeResponse = await payPublishFeeQORT("default", "QORT");
|
||||||
if (!publishFeeResponse) {
|
if (!publishFeeResponse) {
|
||||||
dispatch(
|
dispatch(
|
||||||
setNotification({
|
setNotification({
|
||||||
@ -365,7 +362,7 @@ export const EditIssue = () => {
|
|||||||
action: "PUBLISH_QDN_RESOURCE",
|
action: "PUBLISH_QDN_RESOURCE",
|
||||||
name: name,
|
name: name,
|
||||||
service: "DOCUMENT",
|
service: "DOCUMENT",
|
||||||
file: objectToFile(issueObject),
|
data64: await objectToBase64(issueObject),
|
||||||
title: title.slice(0, 50),
|
title: title.slice(0, 50),
|
||||||
description: metadescription,
|
description: metadescription,
|
||||||
identifier: editIssueProperties.id,
|
identifier: editIssueProperties.id,
|
||||||
|
@ -326,7 +326,7 @@ export const EditPlaylist = () => {
|
|||||||
action: "PUBLISH_QDN_RESOURCE",
|
action: "PUBLISH_QDN_RESOURCE",
|
||||||
name: username,
|
name: username,
|
||||||
service: "PLAYLIST",
|
service: "PLAYLIST",
|
||||||
file: objectToFile(playlistObject),
|
data64: await objectToBase64(playlistObject),
|
||||||
title: title.slice(0, 50),
|
title: title.slice(0, 50),
|
||||||
description: metadescription,
|
description: metadescription,
|
||||||
identifier: identifier,
|
identifier: identifier,
|
||||||
|
@ -21,8 +21,9 @@ import {
|
|||||||
titleFormatter,
|
titleFormatter,
|
||||||
} from "../../constants/Misc.ts";
|
} from "../../constants/Misc.ts";
|
||||||
import {
|
import {
|
||||||
feeAmountBase,
|
appName,
|
||||||
feeDisclaimer,
|
feeDataDefault,
|
||||||
|
feePriceToString,
|
||||||
supportedCoins,
|
supportedCoins,
|
||||||
} from "../../constants/PublishFees/FeeData.tsx";
|
} from "../../constants/PublishFees/FeeData.tsx";
|
||||||
import { CoinType } from "../../constants/PublishFees/FeePricePublish/FeePricePublish.ts";
|
import { CoinType } from "../../constants/PublishFees/FeePricePublish/FeePricePublish.ts";
|
||||||
@ -36,6 +37,7 @@ import { setNotification } from "../../state/features/notificationsSlice";
|
|||||||
import { RootState } from "../../state/store";
|
import { RootState } from "../../state/store";
|
||||||
import { BountyData, validateBountyInput } from "../../utils/qortalRequests.ts";
|
import { BountyData, validateBountyInput } from "../../utils/qortalRequests.ts";
|
||||||
import { objectToBase64, objectToFile } from "../../utils/PublishFormatter.ts";
|
import { objectToBase64, objectToFile } from "../../utils/PublishFormatter.ts";
|
||||||
|
import { StateCheckBox } from "../../utils/StateCheckBox.tsx";
|
||||||
import { isNumber } from "../../utils/utilFunctions.ts";
|
import { isNumber } from "../../utils/utilFunctions.ts";
|
||||||
import {
|
import {
|
||||||
AutocompleteQappNames,
|
AutocompleteQappNames,
|
||||||
@ -116,7 +118,7 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
const [sourceCode, setSourceCode] = useState<string>("");
|
const [sourceCode, setSourceCode] = useState<string>("");
|
||||||
const [coin, setCoin] = useState<CoinType>("QORT");
|
const [coin, setCoin] = useState<CoinType>("QORT");
|
||||||
const [showCoins, setShowCoins] = useState<boolean>(false);
|
const [showCoins, setShowCoins] = useState<boolean>(false);
|
||||||
|
const [payFee, setPayFee] = useState<boolean>(true);
|
||||||
const categoryListRef = useRef<CategoryListRef>(null);
|
const categoryListRef = useRef<CategoryListRef>(null);
|
||||||
const imagePublisherRef = useRef<ImagePublisherRef>(null);
|
const imagePublisherRef = useRef<ImagePublisherRef>(null);
|
||||||
const autocompleteRef = useRef<QappNamesRef>(null);
|
const autocompleteRef = useRef<QappNamesRef>(null);
|
||||||
@ -283,12 +285,14 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
|
|
||||||
const selectedQappName = autocompleteRef?.current?.getSelectedValue();
|
const selectedQappName = autocompleteRef?.current?.getSelectedValue();
|
||||||
|
|
||||||
const publishFeeResponse = await payPublishFeeQORT(feeAmountBase);
|
let publishFeeResponse = undefined;
|
||||||
if (log) console.log("feeResponse: ", publishFeeResponse);
|
|
||||||
|
|
||||||
const feeData: PublishFeeData = {
|
if (payFee) {
|
||||||
|
publishFeeResponse = await payPublishFeeQORT("default", "QORT");
|
||||||
|
if (log) console.log("feeResponse: ", publishFeeResponse);
|
||||||
|
}
|
||||||
|
const feeData = {
|
||||||
signature: publishFeeResponse,
|
signature: publishFeeResponse,
|
||||||
senderName: "",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const isBountyNumber = isNumber(bounty);
|
const isBountyNumber = isNumber(bounty);
|
||||||
@ -311,7 +315,6 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
feeData,
|
feeData,
|
||||||
bountyData,
|
bountyData,
|
||||||
};
|
};
|
||||||
|
|
||||||
const QappNameString = autocompleteRef?.current?.getQappNameFetchString();
|
const QappNameString = autocompleteRef?.current?.getQappNameFetchString();
|
||||||
const categoryString =
|
const categoryString =
|
||||||
categoryListRef.current?.getCategoriesFetchString(categoryList);
|
categoryListRef.current?.getCategoriesFetchString(categoryList);
|
||||||
@ -330,13 +333,15 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
action: "PUBLISH_QDN_RESOURCE",
|
action: "PUBLISH_QDN_RESOURCE",
|
||||||
name: name,
|
name: name,
|
||||||
service: "DOCUMENT",
|
service: "DOCUMENT",
|
||||||
file: objectToFile(issueObject),
|
data64: await objectToBase64(issueObject),
|
||||||
title: title.slice(0, 50),
|
title: title.slice(0, 50),
|
||||||
description: metadescription,
|
description: metadescription,
|
||||||
identifier: identifier + "_metadata",
|
identifier: identifier + "_metadata",
|
||||||
tag1: QSUPPORT_FILE_BASE,
|
tag1: QSUPPORT_FILE_BASE,
|
||||||
filename: `video_metadata.json`,
|
filename: `video_metadata.json`,
|
||||||
};
|
};
|
||||||
|
console.log("issue object: ", issueObject);
|
||||||
|
console.log("request body: ", requestBodyJson);
|
||||||
listOfPublishes.push(requestBodyJson);
|
listOfPublishes.push(requestBodyJson);
|
||||||
|
|
||||||
const multiplePublish = {
|
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>
|
<ActionButtonRow>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -593,7 +603,6 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
</ThemeButtonBright>
|
</ThemeButtonBright>
|
||||||
</Box>
|
</Box>
|
||||||
</ActionButtonRow>
|
</ActionButtonRow>
|
||||||
{feeDisclaimer}
|
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ export const CommentEditor = ({
|
|||||||
action: "PUBLISH_QDN_RESOURCE",
|
action: "PUBLISH_QDN_RESOURCE",
|
||||||
name: name,
|
name: name,
|
||||||
service: "BLOG_COMMENT",
|
service: "BLOG_COMMENT",
|
||||||
file: stringToFile(value),
|
data64: utf8ToBase64(value),
|
||||||
identifier: identifier,
|
identifier: identifier,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -11,3 +11,5 @@ export const QSUPPORT_PLAYLIST_BASE = useTestIdentifiers
|
|||||||
export const QSUPPORT_COMMENT_BASE = useTestIdentifiers
|
export const QSUPPORT_COMMENT_BASE = useTestIdentifiers
|
||||||
? "qcomment_v1_MYTEST_support_"
|
? "qcomment_v1_MYTEST_support_"
|
||||||
: "qcomment_v1_q_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 { Box } from "@mui/material";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { StateCheckBox } from "../../utils/StateCheckBox.tsx";
|
||||||
import { useTestIdentifiers } from "../Identifiers.ts";
|
import { useTestIdentifiers } from "../Identifiers.ts";
|
||||||
|
import {
|
||||||
|
FeePrice,
|
||||||
|
fetchCurrentPriceData,
|
||||||
|
} from "./FeePricePublish/FeePricePublish.ts";
|
||||||
|
|
||||||
export const appName = "Q-Support";
|
export const appName = "Q-Support";
|
||||||
export const feeDestinationName = "Q-Support";
|
export const feeDestinationName = "Q-Support";
|
||||||
|
|
||||||
export const feeAmountBase = useTestIdentifiers ? 0.000001 : 0.25;
|
|
||||||
export const FEE_BASE = useTestIdentifiers
|
export const FEE_BASE = useTestIdentifiers
|
||||||
? "MYTEST_support_fees"
|
? "MYTEST_support_fees"
|
||||||
: "q_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 = [
|
export const supportedCoins = [
|
||||||
"QORT",
|
"QORT",
|
||||||
@ -24,17 +33,26 @@ export const supportedCoins = [
|
|||||||
export const maxFeePublishTimeDiff = 10; // time in minutes before/after publish when fee is considered valid
|
export const maxFeePublishTimeDiff = 10; // time in minutes before/after publish when fee is considered valid
|
||||||
export type FeeType = "default" | "comment" | "like" | "dislike" | "superlike";
|
export type FeeType = "default" | "comment" | "like" | "dislike" | "superlike";
|
||||||
|
|
||||||
export const feeDisclaimerString = `When Publishing (but not editing) Issues ${feeAmountBase} \n
|
// use wherever the fee is communicated to the user
|
||||||
QORT is requested to fund continued development of Q-Support.`;
|
const feeCheckBox = (
|
||||||
|
<StateCheckBox
|
||||||
export const feeDisclaimer = (
|
label={`I agree to pay a fee of ${feePriceToString(feeDataDefault)} to support further development of ${appName}`}
|
||||||
<Box
|
defaultChecked
|
||||||
sx={{
|
/>
|
||||||
fontSize: "28px",
|
|
||||||
color: "#f44336",
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{feeDisclaimerString}
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// // 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";
|
} from "@mui/material";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { SxProps } from "@mui/material/styles";
|
import { SxProps } from "@mui/material/styles";
|
||||||
|
import { FeePrice } from "./FeePricePublish.ts";
|
||||||
|
|
||||||
export interface DataTableProps {
|
export interface DataTableProps {
|
||||||
columnNames: string[];
|
columnNames: string[];
|
||||||
data: string[][];
|
data: FeePrice[];
|
||||||
sx?: SxProps;
|
sx?: SxProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DataTable = ({ columnNames, data, sx }: DataTableProps) => {
|
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 (
|
return (
|
||||||
<TableContainer sx={{ ...sx }}>
|
<TableContainer sx={{ ...sx }}>
|
||||||
<Table align="center" stickyHeader>
|
<Table align="center" stickyHeader>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
{columnNames.map((columnName, index) => (
|
{columnNames.map((columnName, index) => (
|
||||||
<TableCell
|
<TableCell sx={boldSX} key={columnName + index}>
|
||||||
sx={{
|
|
||||||
fontSize: "30px",
|
|
||||||
textAlign: "center",
|
|
||||||
fontWeight: "bold",
|
|
||||||
}}
|
|
||||||
key={columnName + index}
|
|
||||||
>
|
|
||||||
{columnName}
|
{columnName}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
))}
|
))}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{data.map((tableRow, index) => {
|
{data.map((tableRow, rowIndex) => {
|
||||||
return (
|
return (
|
||||||
<TableRow key={tableRow.toString() + index}>
|
<TableRow key={tableRow.toString() + rowIndex}>
|
||||||
{tableRow.map((tableCell, index) => (
|
{<TableCell sx={boldSX}>{rowIndex + 1}</TableCell>}
|
||||||
<TableCell
|
|
||||||
sx={{
|
{Object.values(tableRow).map((tableCell, cellIndex) => (
|
||||||
fontSize: index === 0 ? "30px" : "25px",
|
<TableCell sx={cellSX} key={tableCell + cellIndex}>
|
||||||
fontWeight: index === 0 ? "bold" : "normal",
|
{formatCell(tableCell, cellIndex)}
|
||||||
textAlign: "center",
|
|
||||||
}}
|
|
||||||
key={tableCell + index}
|
|
||||||
>
|
|
||||||
{tableCell}
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
))}
|
))}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
@ -4,21 +4,19 @@ import { appName } from "../FeeData.tsx";
|
|||||||
import { ModalBody } from "./FeePricePublish-styles.tsx";
|
import { ModalBody } from "./FeePricePublish-styles.tsx";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { userHasName } from "../VerifyPayment-Functions.ts";
|
import { userHasName } from "../VerifyPayment-Functions.ts";
|
||||||
import { FeeHistoryTable } from "./FeeHistoryTable.tsx";
|
import { FeeHistoryModalBody } from "./FeeHistoryModalBody.tsx";
|
||||||
|
|
||||||
export const FeeHistoryModal = () => {
|
export const FeeHistoryModal = () => {
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
const [userOwnsApp, setUserOwnsApp] = useState<boolean>(false);
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
useEffect(() => {
|
|
||||||
userHasName(appName).then(userHasName => setUserOwnsApp(userHasName));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const buttonSX = {
|
const buttonSX = {
|
||||||
fontSize: "20px",
|
fontSize: "20px",
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (theme.palette.mode === "light")
|
if (theme.palette.mode === "light")
|
||||||
buttonSX["&:hover"] = { backgroundColor: theme.palette.primary.dark };
|
buttonSX["&:hover"] = { backgroundColor: theme.palette.primary.dark };
|
||||||
|
|
||||||
@ -37,7 +35,7 @@ export const FeeHistoryModal = () => {
|
|||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
<ModalBody sx={{ width: "75vw", maxWidth: "75vw" }}>
|
<ModalBody sx={{ width: "75vw", maxWidth: "75vw" }}>
|
||||||
<FeeHistoryTable />
|
<FeeHistoryModalBody />
|
||||||
<Button sx={buttonSX} onClick={() => setOpen(false)}>
|
<Button sx={buttonSX} onClick={() => setOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</Button>
|
</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 { setFeeData } from "../../../state/features/globalSlice.ts";
|
||||||
import { store } from "../../../state/store.js";
|
import { store } from "../../../state/store.js";
|
||||||
import {
|
import { objectToFile } from "../../../utils/PublishFormatter.ts";
|
||||||
objectToBase64,
|
import { appName, FEE_BASE, FeeType } from "../FeeData.tsx";
|
||||||
objectToFile,
|
|
||||||
} from "../../../utils/PublishFormatter.ts";
|
|
||||||
import { useTestIdentifiers } from "../../Identifiers.ts";
|
|
||||||
import { appName, FEE_BASE, feeAmountBase, FeeType } from "../FeeData.tsx";
|
|
||||||
|
|
||||||
export type CoinType = "QORT" | "BTC" | "LTC" | "DOGE" | "DGB" | "RVN" | "ARRR";
|
export type CoinType = "QORT" | "BTC" | "LTC" | "DOGE" | "DGB" | "RVN" | "ARRR";
|
||||||
|
|
||||||
@ -16,7 +12,7 @@ export interface FeePrice {
|
|||||||
coinType: CoinType;
|
coinType: CoinType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const feesPublishService = "DOCUMENT";
|
export const feesPublishService = "DOCUMENT";
|
||||||
|
|
||||||
export const fetchFees = async () => {
|
export const fetchFees = async () => {
|
||||||
const feeData = store.getState().global.feeData;
|
const feeData = store.getState().global.feeData;
|
||||||
@ -48,45 +44,39 @@ export const fetchFeesRedux = () => {
|
|||||||
fetchFees().then(feeData => store.dispatch(setFeeData(feeData)));
|
fetchFees().then(feeData => store.dispatch(setFeeData(feeData)));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addFeePrice = async (
|
export const fetchCurrentPriceData = async (
|
||||||
feeAmount = feeAmountBase,
|
|
||||||
feeType: FeeType = "default",
|
feeType: FeeType = "default",
|
||||||
coinType: CoinType = "QORT"
|
coinType: CoinType = "QORT"
|
||||||
) => {
|
) => {
|
||||||
const fees = await fetchFees();
|
const fees = await fetchFees();
|
||||||
|
if (fees?.length === 0 || !fees) return undefined;
|
||||||
|
|
||||||
fees.push({
|
const filteredFees = fees.filter(
|
||||||
time: Date.now(),
|
price => price.feeType === feeType && price.coinType === coinType
|
||||||
feeAmount,
|
);
|
||||||
feeType,
|
|
||||||
coinType,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("fees are: ", fees);
|
return filteredFees.at(-1) as FeePrice;
|
||||||
await qortalRequest({
|
|
||||||
action: "PUBLISH_QDN_RESOURCE",
|
|
||||||
name: appName,
|
|
||||||
identifier: FEE_BASE,
|
|
||||||
service: feesPublishService,
|
|
||||||
file: objectToFile(fees),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const feeFilter = (fee: FeePrice, feeToVerify: FeePrice) => {
|
const feeFilter = (fee: FeePrice, feeToVerify: FeePrice) => {
|
||||||
const nameCheck = fee.feeType === feeToVerify.feeType;
|
const nameCheck = fee.feeType === feeToVerify.feeType;
|
||||||
const coinTypeCheck = fee.coinType === feeToVerify.coinType;
|
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) => {
|
export const verifyFeeAmount = async (feeToVerify: FeePrice) => {
|
||||||
if (useTestIdentifiers) return true;
|
|
||||||
|
|
||||||
const fees = await fetchFees();
|
const fees = await fetchFees();
|
||||||
const filteredFees = fees.filter(fee => feeFilter(fee, feeToVerify));
|
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 isFeeAmountValid;
|
||||||
return feeToVerify.feeAmount >= feeToCheck.feeAmount;
|
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import { feeDestinationName, FeeType } from "./FeeData.tsx";
|
import { feeDestinationName, FeeType } from "./FeeData.tsx";
|
||||||
import { CoinType } from "./FeePricePublish/FeePricePublish.ts";
|
import {
|
||||||
|
CoinType,
|
||||||
|
fetchCurrentPriceData,
|
||||||
|
} from "./FeePricePublish/FeePricePublish.ts";
|
||||||
|
|
||||||
export interface NameData {
|
export interface NameData {
|
||||||
name: string;
|
name: string;
|
||||||
@ -58,7 +61,7 @@ export const sendQORTtoName = async (name: string, amount: number) => {
|
|||||||
|
|
||||||
export interface PublishFeeData {
|
export interface PublishFeeData {
|
||||||
signature: string;
|
signature: string;
|
||||||
senderName: string;
|
senderName?: string;
|
||||||
createdTimestamp?: number; //timestamp of the metadata publish, NOT the send feeAmount publish, added after publish is fetched
|
createdTimestamp?: number; //timestamp of the metadata publish, NOT the send feeAmount publish, added after publish is fetched
|
||||||
updatedTimestamp?: number;
|
updatedTimestamp?: number;
|
||||||
feeType?: FeeType;
|
feeType?: FeeType;
|
||||||
@ -73,7 +76,11 @@ export interface CommentObject {
|
|||||||
feeData: PublishFeeData;
|
feeData: PublishFeeData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const payPublishFeeQORT = async (feeAmount: number) => {
|
export const payPublishFeeQORT = async (
|
||||||
const publish = await sendQORTtoName(feeDestinationName, feeAmount);
|
feeType: FeeType = "default",
|
||||||
|
coinType: CoinType = "QORT"
|
||||||
|
) => {
|
||||||
|
const feeData = await fetchCurrentPriceData(feeType, coinType);
|
||||||
|
const publish = await sendQORTtoName(feeDestinationName, feeData.feeAmount);
|
||||||
return publish?.signature;
|
return publish?.signature;
|
||||||
};
|
};
|
||||||
|
@ -45,7 +45,7 @@ const verifySignature = async (feeData: PublishFeeData) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const signatureTime = signatureData.timestamp;
|
const signatureTime = signatureData.timestamp;
|
||||||
let doesTimeMatch: boolean = false;
|
let doesTimeMatch = false;
|
||||||
if (!updatedTimestamp) {
|
if (!updatedTimestamp) {
|
||||||
const timeDiff = createdTimestamp - signatureTime;
|
const timeDiff = createdTimestamp - signatureTime;
|
||||||
const timeDiffMinutes = Math.abs(timeDiff) / 1000 / 60;
|
const timeDiffMinutes = Math.abs(timeDiff) / 1000 / 60;
|
||||||
|
@ -10,19 +10,20 @@ import React, { useRef, useState } from "react";
|
|||||||
|
|
||||||
type eventType = React.ChangeEvent<HTMLInputElement>;
|
type eventType = React.ChangeEvent<HTMLInputElement>;
|
||||||
type BoundedNumericTextFieldProps = {
|
type BoundedNumericTextFieldProps = {
|
||||||
minValue: number;
|
minValue?: number;
|
||||||
maxValue: number;
|
maxValue?: number;
|
||||||
addIconButtons?: boolean;
|
addIconButtons?: boolean;
|
||||||
allowDecimals?: boolean;
|
allowDecimals?: boolean;
|
||||||
allowNegatives?: boolean;
|
allowNegatives?: boolean;
|
||||||
onChange?: (s: string) => void;
|
onChange?: (s: string) => void;
|
||||||
|
onBlur?: (s: string) => void;
|
||||||
initialValue?: string;
|
initialValue?: string;
|
||||||
maxSigDigits?: number;
|
maxSigDigits?: number;
|
||||||
} & TextFieldProps;
|
} & TextFieldProps;
|
||||||
|
|
||||||
export const BoundedNumericTextField = ({
|
export const BoundedNumericTextField = ({
|
||||||
minValue,
|
minValue = 0,
|
||||||
maxValue,
|
maxValue = Number.MAX_VALUE,
|
||||||
addIconButtons = true,
|
addIconButtons = true,
|
||||||
allowDecimals = true,
|
allowDecimals = true,
|
||||||
allowNegatives = false,
|
allowNegatives = false,
|
||||||
@ -30,6 +31,8 @@ export const BoundedNumericTextField = ({
|
|||||||
maxSigDigits = 6,
|
maxSigDigits = 6,
|
||||||
...props
|
...props
|
||||||
}: BoundedNumericTextFieldProps) => {
|
}: BoundedNumericTextFieldProps) => {
|
||||||
|
const { onChange, onBlur, ...noChangeProps } = { ...props };
|
||||||
|
|
||||||
const [textFieldValue, setTextFieldValue] = useState<string>(
|
const [textFieldValue, setTextFieldValue] = useState<string>(
|
||||||
initialValue || ""
|
initialValue || ""
|
||||||
);
|
);
|
||||||
@ -108,6 +111,7 @@ export const BoundedNumericTextField = ({
|
|||||||
let value = e.target.value;
|
let value = e.target.value;
|
||||||
if (stringIsEmpty(value) || value === ".") {
|
if (stringIsEmpty(value) || value === ".") {
|
||||||
setTextFieldValue("");
|
setTextFieldValue("");
|
||||||
|
if (onBlur) onBlur("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,9 +120,9 @@ export const BoundedNumericTextField = ({
|
|||||||
if (isAllZerosNum.test(value)) value = minValue.toString();
|
if (isAllZerosNum.test(value)) value = minValue.toString();
|
||||||
|
|
||||||
setTextFieldValue(value);
|
setTextFieldValue(value);
|
||||||
|
if (onBlur) onBlur(value);
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { onChange, ...noChangeProps } = { ...props };
|
|
||||||
return (
|
return (
|
||||||
<TextField
|
<TextField
|
||||||
{...noChangeProps}
|
{...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 splitLink = crowdfundLink.split("/");
|
||||||
const name = splitLink[5];
|
const name = splitLink[5];
|
||||||
const identifier = splitLink[6];
|
const identifier = splitLink[6];
|
||||||
console.log("fetching crowdfund");
|
|
||||||
return await qortalRequest({
|
return await qortalRequest({
|
||||||
action: "FETCH_QDN_RESOURCE",
|
action: "FETCH_QDN_RESOURCE",
|
||||||
service: "DOCUMENT",
|
service: "DOCUMENT",
|
||||||
|
@ -28,3 +28,12 @@ export const isNumber = (input: string) => {
|
|||||||
export const truncateNumber = (value: string | number, sigDigits: number) => {
|
export const truncateNumber = (value: string | number, sigDigits: number) => {
|
||||||
return Number(value).toFixed(sigDigits);
|
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;
|
||||||
|
};
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from "vite";
|
||||||
import react from '@vitejs/plugin-react'
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
base: ""
|
base: "",
|
||||||
})
|
build: {
|
||||||
|
target: "esnext", //browsers can handle the latest ES features
|
||||||
|
},
|
||||||
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user