New invite minter feature - new scrollTotop button - fixed image embeds on Admin Room in Forum, various other fixes and cleanup.
This commit is contained in:
@@ -99,11 +99,10 @@ const loadAdminBoardPage = async () => {
|
||||
document.getElementById("publish-card-form").addEventListener("submit", async (event) => {
|
||||
event.preventDefault()
|
||||
const isTopicChecked = document.getElementById("topic-checkbox").checked
|
||||
|
||||
// Pass that boolean to publishEncryptedCard
|
||||
await publishEncryptedCard(isTopicChecked)
|
||||
})
|
||||
|
||||
createScrollToTopButton()
|
||||
// await fetchAndValidateAllAdminCards()
|
||||
await fetchAllEncryptedCards()
|
||||
await updateOrSaveAdminGroupsDataLocally()
|
||||
|
@@ -4,6 +4,9 @@ const cardIdentifierPrefix = "Minter-board-card"
|
||||
let isExistingCard = false
|
||||
let existingCardData = {}
|
||||
let existingCardIdentifier = {}
|
||||
const MIN_ADMIN_YES_VOTES = 9;
|
||||
const MINTER_INVITE_BLOCK_HEIGHT = 9999999; // Example height, update later
|
||||
let isApproved = false
|
||||
|
||||
const loadMinterBoardPage = async () => {
|
||||
// Clear existing content on the page
|
||||
@@ -28,7 +31,7 @@ const loadMinterBoardPage = async () => {
|
||||
<div id="cards-container" class="cards-container" style="margin-top: 20px;"></div>
|
||||
<div id="publish-card-view" class="publish-card-view" style="display: none; text-align: left; padding: 20px;">
|
||||
<form id="publish-card-form">
|
||||
<h3>Create or Update Your Minter Card</h3>
|
||||
<h3>Create or Update Your Card</h3>
|
||||
<label for="card-header">Header:</label>
|
||||
<input type="text" id="card-header" maxlength="100" placeholder="Enter card header" required>
|
||||
<label for="card-content">Content:</label>
|
||||
@@ -45,6 +48,7 @@ const loadMinterBoardPage = async () => {
|
||||
</div>
|
||||
`
|
||||
document.body.appendChild(mainContent)
|
||||
createScrollToTopButton()
|
||||
|
||||
document.getElementById("publish-card-button").addEventListener("click", async () => {
|
||||
try {
|
||||
@@ -331,7 +335,7 @@ const fetchExistingCard = async () => {
|
||||
return null
|
||||
} else if (response.length === 1) { // we don't need to go through all of the rest of the checks and filtering nonsense if there's only a single result, just return it.
|
||||
const mostRecentCard = response[0]
|
||||
|
||||
isExistingCard = true
|
||||
const cardDataResponse = await qortalRequest({
|
||||
action: "FETCH_QDN_RESOURCE",
|
||||
name: userState.accountName, // User's account name
|
||||
@@ -341,6 +345,7 @@ const fetchExistingCard = async () => {
|
||||
|
||||
existingCardIdentifier = mostRecentCard.identifier
|
||||
existingCardData = cardDataResponse
|
||||
isExistingCard = true
|
||||
|
||||
return cardDataResponse
|
||||
}
|
||||
@@ -367,6 +372,7 @@ const fetchExistingCard = async () => {
|
||||
|
||||
existingCardIdentifier = mostRecentCard.identifier
|
||||
existingCardData = cardDataResponse
|
||||
isExistingCard = true
|
||||
|
||||
console.log("Full card data fetched successfully:", cardDataResponse)
|
||||
|
||||
@@ -472,6 +478,7 @@ const publishCard = async () => {
|
||||
|
||||
if (isExistingCard){
|
||||
alert("Card Updated Successfully! (No poll updates are possible at this time...)")
|
||||
isExistingCard = false
|
||||
}
|
||||
|
||||
document.getElementById("publish-card-form").reset()
|
||||
@@ -575,29 +582,6 @@ const processPollData= async (pollData, minterGroupMembers, minterAdmins, creato
|
||||
blocksMinted
|
||||
}
|
||||
})
|
||||
//TODO verify this new voterPromises async function works better.
|
||||
// const voterPromises = pollData.votes.map(async (vote) => {
|
||||
// const voterPublicKey = vote.voterPublicKey;
|
||||
// const voterAddress = await getAddressFromPublicKey(voterPublicKey);
|
||||
|
||||
// const [nameInfo, addressInfo] = await Promise.all([
|
||||
// getNameFromAddress(voterAddress).catch(() => ""),
|
||||
// getAddressInfo(voterAddress).catch(() => ({})),
|
||||
// ]);
|
||||
|
||||
// const voterName = nameInfo || (nameInfo === voterAddress ? "" : voterAddress);
|
||||
// const blocksMinted = addressInfo?.blocksMinted || 0;
|
||||
|
||||
// return {
|
||||
// optionIndex: vote.optionIndex,
|
||||
// voterPublicKey,
|
||||
// voterAddress,
|
||||
// voterName,
|
||||
// isAdmin: adminAddresses.includes(voterAddress),
|
||||
// isMinter: memberAddresses.includes(voterAddress),
|
||||
// blocksMinted,
|
||||
// };
|
||||
// });
|
||||
|
||||
const allVoters = await Promise.all(voterPromises)
|
||||
const yesVoters = []
|
||||
@@ -774,35 +758,6 @@ const fetchCommentsForCard = async (cardIdentifier) => {
|
||||
}
|
||||
}
|
||||
|
||||
// display the comments on the card, with passed cardIdentifier to identify the card --------------
|
||||
// const displayComments = async (cardIdentifier) => {
|
||||
// try {
|
||||
// const comments = await fetchCommentsForCard(cardIdentifier);
|
||||
// const commentsContainer = document.getElementById(`comments-container-${cardIdentifier}`)
|
||||
|
||||
// for (const comment of comments) {
|
||||
// const commentDataResponse = await qortalRequest({
|
||||
// action: "FETCH_QDN_RESOURCE",
|
||||
// name: comment.name,
|
||||
// service: "BLOG_POST",
|
||||
// identifier: comment.identifier,
|
||||
// })
|
||||
// const timestamp = await timestampToHumanReadableDate(commentDataResponse.timestamp)
|
||||
// const commentHTML = `
|
||||
// <div class="comment" style="border: 1px solid gray; margin: 1vh 0; padding: 1vh; background: #1c1c1c;">
|
||||
// <p><strong><u>${commentDataResponse.creator}</strong>:</p></u>
|
||||
// <p>${commentDataResponse.content}</p>
|
||||
// <p><i>${timestamp}</p></i>
|
||||
// </div>
|
||||
// `
|
||||
// commentsContainer.insertAdjacentHTML('beforeend', commentHTML)
|
||||
// }
|
||||
|
||||
// } catch (error) {
|
||||
// console.error(`Error displaying comments (or no comments) for ${cardIdentifier}:`, error)
|
||||
// }
|
||||
// }
|
||||
|
||||
const displayComments = async (cardIdentifier) => {
|
||||
try {
|
||||
const comments = await fetchCommentsForCard(cardIdentifier)
|
||||
@@ -902,6 +857,8 @@ const countComments = async (cardIdentifier) => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const createModal = (modalType='') => {
|
||||
if (document.getElementById(`${modalType}-modal`)) {
|
||||
return
|
||||
@@ -1039,6 +996,68 @@ const generateDarkPastelBackgroundBy = (name) => {
|
||||
return `hsl(${hue}, ${saturation}%, ${lightness}%)`
|
||||
}
|
||||
|
||||
const handleInviteMinter = async (minterName) => {
|
||||
try {
|
||||
const blockInfo = await getLatestBlockInfo()
|
||||
const blockHeight = toString(blockInfo.height)
|
||||
if (blockHeight <= MINTER_INVITE_BLOCK_HEIGHT) {
|
||||
console.log(`block height is under the featureTrigger height`)
|
||||
}
|
||||
const minterAccountInfo = await getNameInfo(minterName)
|
||||
const minterAddress = await minterAccountInfo.owner
|
||||
const adminPublicKey = await getPublicKeyByName(userState.accountName)
|
||||
console.log(`about to attempt group invite, minterAddress: ${minterAddress}, adminPublicKey: ${adminPublicKey}`)
|
||||
const inviteTransaction = await createGroupInviteTransaction(minterAddress, adminPublicKey, 694, minterAddress, 864000, 0)
|
||||
|
||||
// Step 2: Sign the transaction using qortalRequest
|
||||
const signedTransaction = await qortalRequest({
|
||||
action: "SIGN_TRANSACTION",
|
||||
unsignedBytes: inviteTransaction
|
||||
})
|
||||
|
||||
// Step 3: Process the transaction
|
||||
console.warn(`signed transaction`,signedTransaction)
|
||||
const processResponse = await processTransaction(signedTransaction)
|
||||
|
||||
if (processResponse?.status === "OK") {
|
||||
alert(`${minterName} has been successfully invited!`)
|
||||
} else {
|
||||
alert("Failed to process the invite transaction.")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error inviting minter:", error)
|
||||
alert("Error inviting minter. Please try again.")
|
||||
}
|
||||
}
|
||||
|
||||
const createInviteButtonHtml = (creator, cardIdentifier) => {
|
||||
return `
|
||||
<div id="invite-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||
<button onclick="handleInviteMinter('${creator}')"
|
||||
style="padding: 10px; background:rgb(0, 109, 76) ; color: white; border: dotted; cursor: pointer; border-radius: 5px;"
|
||||
onmouseover="this.style.backgroundColor='rgb(25, 47, 39) '"
|
||||
onmouseout="this.style.backgroundColor='rgba(7, 122, 101, 0.63) '"
|
||||
>
|
||||
Invite Minter
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) => {
|
||||
const latestBlockInfo = await getLatestBlockInfo()
|
||||
const isBlockPassed = latestBlockInfo.height > MINTER_INVITE_BLOCK_HEIGHT
|
||||
|
||||
if (adminYes >= 9 && userState.isMinterAdmin) {
|
||||
const inviteButtonHtml = createInviteButtonHtml(creator, cardIdentifier)
|
||||
console.log(`admin votes over 9, creating invite button...`, adminYes)
|
||||
return inviteButtonHtml
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
// Create the overall Minter Card HTML -----------------------------------------------
|
||||
const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCount, cardUpdatedTime, BgColor) => {
|
||||
const { header, content, links, creator, timestamp, poll } = cardData
|
||||
@@ -1056,6 +1075,9 @@ const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCoun
|
||||
createModal('links')
|
||||
createModal('poll-details')
|
||||
|
||||
const inviteButtonHtml = await checkAndDisplayInviteButton(adminYes, creator, cardIdentifier)
|
||||
const inviteHtmlAdd = (inviteButtonHtml) ? inviteButtonHtml : ''
|
||||
|
||||
return `
|
||||
<div class="minter-card" style="background-color: ${BgColor}">
|
||||
<div class="minter-card-header">
|
||||
@@ -1063,20 +1085,21 @@ const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCoun
|
||||
<h3>${creator}</h3>
|
||||
<p>${header}</p>
|
||||
</div>
|
||||
<div class="support-header"><h5>MINTER'S POST</h5></div>
|
||||
<div class="support-header"><h5>USER'S POST</h5></div>
|
||||
<div class="info">
|
||||
${content}
|
||||
</div>
|
||||
<div class="support-header"><h5>MINTER'S LINKS</h5></div>
|
||||
<div class="support-header"><h5>USER'S LINKS</h5></div>
|
||||
<div class="info-links">
|
||||
${linksHTML}
|
||||
</div>
|
||||
<div class="results-header support-header"><h5>CURRENT RESULTS</h5></div>
|
||||
<div class="results-header support-header"><h5>CURRENT SUPPORT RESULTS</h5></div>
|
||||
<div class="minter-card-results">
|
||||
<button onclick="togglePollDetails('${cardIdentifier}')">Display Poll Details</button>
|
||||
<div id="poll-details-${cardIdentifier}" style="display: none;">
|
||||
${detailsHtml}
|
||||
</div>
|
||||
${inviteHtmlAdd}
|
||||
<div class="admin-results">
|
||||
<span class="admin-yes">Admin Yes: ${adminYes}</span>
|
||||
<span class="admin-no">Admin No: ${adminNo}</span>
|
||||
@@ -1092,7 +1115,7 @@ const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCoun
|
||||
<span class="total-no">Weight: ${totalNoWeight}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="support-header"><h5>SUPPORT</h5><h5 style="color: #ffae42;">${creator}</h5>
|
||||
<div class="support-header"><h5>SUPPORT ACTION FOR </h5><h5 style="color: #ffae42;">${creator}</h5>
|
||||
<p style="color: #c7c7c7; font-size: .65rem; margin-top: 1vh">(click COMMENTS button to open/close card comments)</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
|
@@ -1,12 +1,12 @@
|
||||
const messageIdentifierPrefix = `mintership-forum-message`;
|
||||
const messageAttachmentIdentifierPrefix = `mintership-forum-attachment`;
|
||||
const messageIdentifierPrefix = `mintership-forum-message`
|
||||
const messageAttachmentIdentifierPrefix = `mintership-forum-attachment`
|
||||
|
||||
// NOTE - SET adminGroups in QortalApi.js to enable admin access to forum for specific groups. Minter Admins will be fetched automatically.
|
||||
|
||||
let replyToMessageIdentifier = null;
|
||||
let latestMessageIdentifiers = {}; // To keep track of the latest message in each room
|
||||
let currentPage = 0; // Track current pagination page
|
||||
let existingIdentifiers = new Set(); // Keep track of existing identifiers to not pull them more than once.
|
||||
let replyToMessageIdentifier = null
|
||||
let latestMessageIdentifiers = {} // To keep track of the latest message in each room
|
||||
let currentPage = 0 // Track current pagination page
|
||||
let existingIdentifiers = new Set() // Keep track of existing identifiers to not pull them more than once.
|
||||
|
||||
let messagesById = {}
|
||||
let messageOrder =[]
|
||||
@@ -22,38 +22,38 @@ const storeMessageInMap = (msg) => {
|
||||
messagesById[msg.identifier] = msg
|
||||
// We will keep an array 'messageOrder' to store the messages and limit the size they take
|
||||
messageOrder.push({ identifier: msg.identifier, timestamp: msg.timestamp })
|
||||
messageOrder.sort((a, b) => a.timestamp - b.timestamp);
|
||||
messageOrder.sort((a, b) => a.timestamp - b.timestamp)
|
||||
|
||||
while (messageOrder.length > MAX_MESSAGES) {
|
||||
// Remove oldest from the front
|
||||
const oldest = messageOrder.shift();
|
||||
const oldest = messageOrder.shift()
|
||||
// Delete from the map as well
|
||||
delete messagesById[oldest.identifier];
|
||||
delete messagesById[oldest.identifier]
|
||||
}
|
||||
}
|
||||
|
||||
function saveMessagesToLocalStorage() {
|
||||
try {
|
||||
const data = { messagesById, messageOrder };
|
||||
localStorage.setItem("forumMessages", JSON.stringify(data));
|
||||
console.log("Saved messages to localStorage. Count:", messageOrder.length);
|
||||
const data = { messagesById, messageOrder }
|
||||
localStorage.setItem("forumMessages", JSON.stringify(data))
|
||||
console.log("Saved messages to localStorage. Count:", messageOrder.length)
|
||||
} catch (error) {
|
||||
console.error("Error saving to localStorage:", error);
|
||||
console.error("Error saving to localStorage:", error)
|
||||
}
|
||||
}
|
||||
|
||||
function loadMessagesFromLocalStorage() {
|
||||
try {
|
||||
const stored = localStorage.getItem("forumMessages");
|
||||
const stored = localStorage.getItem("forumMessages")
|
||||
if (!stored) {
|
||||
console.log("No saved messages in localStorage.");
|
||||
console.log("No saved messages in localStorage.")
|
||||
return;
|
||||
}
|
||||
const parsed = JSON.parse(stored);
|
||||
if (parsed.messagesById && parsed.messageOrder) {
|
||||
messagesById = parsed.messagesById;
|
||||
messageOrder = parsed.messageOrder;
|
||||
console.log(`Loaded ${messageOrder.length} messages from localStorage.`);
|
||||
console.log(`Loaded ${messageOrder.length} messages from localStorage.`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading messages from localStorage:", error);
|
||||
@@ -61,40 +61,42 @@ function loadMessagesFromLocalStorage() {
|
||||
}
|
||||
|
||||
if (localStorage.getItem("latestMessageIdentifiers")) {
|
||||
latestMessageIdentifiers = JSON.parse(localStorage.getItem("latestMessageIdentifiers"));
|
||||
latestMessageIdentifiers = JSON.parse(localStorage.getItem("latestMessageIdentifiers"))
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
console.log("DOMContentLoaded fired!");
|
||||
console.log("DOMContentLoaded fired!")
|
||||
|
||||
// --- GENERAL LINKS (MINTERSHIP-FORUM and MINTER-BOARD) ---
|
||||
const mintershipForumLinks = document.querySelectorAll('a[href="MINTERSHIP-FORUM"]');
|
||||
const mintershipForumLinks = document.querySelectorAll('a[href="MINTERSHIP-FORUM"]')
|
||||
mintershipForumLinks.forEach(link => {
|
||||
link.addEventListener('click', async (event) => {
|
||||
event.preventDefault();
|
||||
event.preventDefault()
|
||||
if (!userState.isLoggedIn) {
|
||||
await login();
|
||||
await login()
|
||||
}
|
||||
await loadForumPage();
|
||||
loadRoomContent("general");
|
||||
startPollingForNewMessages();
|
||||
});
|
||||
});
|
||||
loadRoomContent("general")
|
||||
startPollingForNewMessages()
|
||||
createScrollToTopButton()
|
||||
})
|
||||
})
|
||||
|
||||
const minterBoardLinks = document.querySelectorAll('a[href="MINTER-BOARD"], a[href="MINTERS"]');
|
||||
const minterBoardLinks = document.querySelectorAll('a[href="MINTER-BOARD"], a[href="MINTERS"]')
|
||||
minterBoardLinks.forEach(link => {
|
||||
link.addEventListener("click", async (event) => {
|
||||
event.preventDefault();
|
||||
if (!userState.isLoggedIn) {
|
||||
await login();
|
||||
await login()
|
||||
}
|
||||
if (typeof loadMinterBoardPage === "undefined") {
|
||||
console.log("loadMinterBoardPage not found, loading script dynamically...");
|
||||
await loadScript("./assets/js/MinterBoard.js");
|
||||
console.log("loadMinterBoardPage not found, loading script dynamically...")
|
||||
await loadScript("./assets/js/MinterBoard.js")
|
||||
}
|
||||
await loadMinterBoardPage();
|
||||
});
|
||||
});
|
||||
await loadMinterBoardPage()
|
||||
createScrollToTopButton()
|
||||
})
|
||||
})
|
||||
|
||||
// --- ADMIN CHECK ---
|
||||
await verifyUserIsAdmin();
|
||||
@@ -115,74 +117,74 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
}
|
||||
|
||||
if (userState.isAdmin) {
|
||||
console.log(`User is an Admin. Admin-specific buttons will remain visible.`);
|
||||
console.log(`User is an Admin. Admin-specific buttons will remain visible.`)
|
||||
|
||||
// DATA-BOARD Links for Admins
|
||||
const minterDataBoardLinks = document.querySelectorAll('a[href="ADMINBOARD"]');
|
||||
const minterDataBoardLinks = document.querySelectorAll('a[href="ADMINBOARD"]')
|
||||
minterDataBoardLinks.forEach(link => {
|
||||
link.addEventListener("click", async (event) => {
|
||||
event.preventDefault();
|
||||
event.preventDefault()
|
||||
if (!userState.isLoggedIn) {
|
||||
await login();
|
||||
await login()
|
||||
}
|
||||
if (typeof loadAdminBoardPage === "undefined") {
|
||||
console.log("loadAdminBoardPage function not found, loading script dynamically...");
|
||||
await loadScript("./assets/js/AdminBoard.js");
|
||||
console.log("loadAdminBoardPage function not found, loading script dynamically...")
|
||||
await loadScript("./assets/js/AdminBoard.js")
|
||||
}
|
||||
await loadAdminBoardPage();
|
||||
});
|
||||
});
|
||||
await loadAdminBoardPage()
|
||||
})
|
||||
})
|
||||
|
||||
// TOOLS Links for Admins
|
||||
const toolsLinks = document.querySelectorAll('a[href="TOOLS"]');
|
||||
const toolsLinks = document.querySelectorAll('a[href="TOOLS"]')
|
||||
toolsLinks.forEach(link => {
|
||||
link.addEventListener('click', async (event) => {
|
||||
event.preventDefault();
|
||||
event.preventDefault()
|
||||
if (!userState.isLoggedIn) {
|
||||
await login();
|
||||
await login()
|
||||
}
|
||||
if (typeof loadMinterAdminToolsPage === "undefined") {
|
||||
console.log("loadMinterAdminToolsPage function not found, loading script dynamically...");
|
||||
await loadScript("./assets/js/AdminTools.js");
|
||||
console.log("loadMinterAdminToolsPage function not found, loading script dynamically...")
|
||||
await loadScript("./assets/js/AdminTools.js")
|
||||
}
|
||||
await loadMinterAdminToolsPage();
|
||||
});
|
||||
});
|
||||
await loadMinterAdminToolsPage()
|
||||
})
|
||||
})
|
||||
|
||||
} else {
|
||||
console.log("User is NOT an Admin. Removing admin-specific links.");
|
||||
console.log("User is NOT an Admin. Removing admin-specific links.")
|
||||
|
||||
// Remove all admin-specific links and their parents
|
||||
const toolsLinks = document.querySelectorAll('a[href="TOOLS"], a[href="ADMINBOARD"]');
|
||||
const toolsLinks = document.querySelectorAll('a[href="TOOLS"], a[href="ADMINBOARD"]')
|
||||
toolsLinks.forEach(link => {
|
||||
const buttonParent = link.closest('button');
|
||||
if (buttonParent) buttonParent.remove();
|
||||
const buttonParent = link.closest('button')
|
||||
if (buttonParent) buttonParent.remove()
|
||||
|
||||
const cardParent = link.closest('.item.features-image');
|
||||
if (cardParent) cardParent.remove();
|
||||
const cardParent = link.closest('.item.features-image')
|
||||
if (cardParent) cardParent.remove()
|
||||
|
||||
link.remove();
|
||||
});
|
||||
link.remove()
|
||||
})
|
||||
|
||||
// Center the remaining card if it exists
|
||||
const remainingCard = document.querySelector('.features7 .row .item.features-image');
|
||||
const remainingCard = document.querySelector('.features7 .row .item.features-image')
|
||||
if (remainingCard) {
|
||||
remainingCard.classList.remove('col-lg-6', 'col-md-6');
|
||||
remainingCard.classList.add('col-12', 'text-center');
|
||||
remainingCard.classList.remove('col-lg-6', 'col-md-6')
|
||||
remainingCard.classList.add('col-12', 'text-center')
|
||||
}
|
||||
}
|
||||
|
||||
console.log("All DOMContentLoaded tasks completed.");
|
||||
});
|
||||
console.log("All DOMContentLoaded tasks completed.")
|
||||
})
|
||||
|
||||
async function loadScript(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script");
|
||||
script.src = src;
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
const script = document.createElement("script")
|
||||
script.src = src
|
||||
script.onload = resolve
|
||||
script.onerror = reject
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -200,14 +202,14 @@ const loadForumPage = async () => {
|
||||
if ((typeof userState.isAdmin === 'undefined') || (!userState.isAdmin)){
|
||||
try {
|
||||
// Fetch and verify the admin status asynchronously
|
||||
userState.isAdmin = await verifyUserIsAdmin();
|
||||
userState.isAdmin = await verifyUserIsAdmin()
|
||||
} catch (error) {
|
||||
console.error('Error verifying admin status:', error);
|
||||
console.error('Error verifying admin status:', error)
|
||||
userState.isAdmin = false; // Default to non-admin if there's an issue
|
||||
}
|
||||
}
|
||||
|
||||
const avatarUrl = `/arbitrary/THUMBNAIL/${userState.accountName}/qortal_avatar`;
|
||||
const avatarUrl = `/arbitrary/THUMBNAIL/${userState.accountName}/qortal_avatar`
|
||||
const isAdmin = userState.isAdmin;
|
||||
|
||||
// Create the forum layout, including a header, sub-menu, and keeping the original background image: style="background-image: url('/assets/images/background.jpg');">
|
||||
@@ -229,7 +231,7 @@ const loadForumPage = async () => {
|
||||
</div>
|
||||
<div id="forum-content" class="forum-content"></div>
|
||||
</div>
|
||||
`;
|
||||
`
|
||||
|
||||
document.body.appendChild(mainContent);
|
||||
|
||||
@@ -252,61 +254,61 @@ const loadForumPage = async () => {
|
||||
|
||||
// Function to add the pagination buttons and related control mechanisms ------------------------
|
||||
const renderPaginationControls = (room, totalMessages, limit) => {
|
||||
const paginationContainer = document.getElementById("pagination-container");
|
||||
if (!paginationContainer) return;
|
||||
const paginationContainer = document.getElementById("pagination-container")
|
||||
if (!paginationContainer) return
|
||||
|
||||
paginationContainer.innerHTML = ""; // Clear existing buttons
|
||||
paginationContainer.innerHTML = "" // Clear existing buttons
|
||||
|
||||
const totalPages = Math.ceil(totalMessages / limit);
|
||||
const totalPages = Math.ceil(totalMessages / limit)
|
||||
|
||||
// Add "Previous" button
|
||||
if (currentPage > 0) {
|
||||
const prevButton = document.createElement("button");
|
||||
prevButton.innerText = "Previous";
|
||||
const prevButton = document.createElement("button")
|
||||
prevButton.innerText = "Previous"
|
||||
prevButton.addEventListener("click", () => {
|
||||
if (currentPage > 0) {
|
||||
currentPage--;
|
||||
loadMessagesFromQDN(room, currentPage, false);
|
||||
currentPage--
|
||||
loadMessagesFromQDN(room, currentPage, false)
|
||||
}
|
||||
});
|
||||
paginationContainer.appendChild(prevButton);
|
||||
})
|
||||
paginationContainer.appendChild(prevButton)
|
||||
}
|
||||
|
||||
// Add numbered page buttons
|
||||
for (let i = 0; i < totalPages; i++) {
|
||||
const pageButton = document.createElement("button");
|
||||
pageButton.innerText = i + 1;
|
||||
pageButton.className = i === currentPage ? "active-page" : "";
|
||||
const pageButton = document.createElement("button")
|
||||
pageButton.innerText = i + 1
|
||||
pageButton.className = i === currentPage ? "active-page" : ""
|
||||
pageButton.addEventListener("click", () => {
|
||||
if (i !== currentPage) {
|
||||
currentPage = i;
|
||||
loadMessagesFromQDN(room, currentPage, false);
|
||||
currentPage = i
|
||||
loadMessagesFromQDN(room, currentPage, false)
|
||||
}
|
||||
});
|
||||
paginationContainer.appendChild(pageButton);
|
||||
})
|
||||
paginationContainer.appendChild(pageButton)
|
||||
}
|
||||
|
||||
// Add "Next" button
|
||||
if (currentPage < totalPages - 1) {
|
||||
const nextButton = document.createElement("button");
|
||||
const nextButton = document.createElement("button")
|
||||
nextButton.innerText = "Next";
|
||||
nextButton.addEventListener("click", () => {
|
||||
if (currentPage < totalPages - 1) {
|
||||
currentPage++;
|
||||
currentPage++
|
||||
loadMessagesFromQDN(room, currentPage, false);
|
||||
}
|
||||
});
|
||||
paginationContainer.appendChild(nextButton);
|
||||
})
|
||||
paginationContainer.appendChild(nextButton)
|
||||
}
|
||||
}
|
||||
|
||||
// Main function to load the full content of the room, along with all main functionality -----------------------------------
|
||||
const loadRoomContent = async (room) => {
|
||||
const forumContent = document.getElementById("forum-content");
|
||||
const forumContent = document.getElementById("forum-content")
|
||||
|
||||
if (!forumContent) {
|
||||
console.error("Forum content container not found!");
|
||||
return;
|
||||
console.error("Forum content container not found!")
|
||||
return
|
||||
}
|
||||
|
||||
if (userState.isAdmin) {
|
||||
@@ -333,7 +335,7 @@ const loadRoomContent = async (room) => {
|
||||
<button id="send-button" class="send-button">Publish</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
`
|
||||
|
||||
// Add modal for image preview
|
||||
forumContent.insertAdjacentHTML(
|
||||
@@ -345,13 +347,13 @@ const loadRoomContent = async (room) => {
|
||||
<div id="caption" class="caption"></div>
|
||||
<button id="download-button" class="download-button">Download</button>
|
||||
</div>
|
||||
`);
|
||||
`)
|
||||
|
||||
initializeQuillEditor();
|
||||
setupModalHandlers();
|
||||
setupFileInputs(room);
|
||||
initializeQuillEditor()
|
||||
setupModalHandlers()
|
||||
setupFileInputs(room)
|
||||
//TODO - maybe turn this into its own function and put it as a button? But for now it's fine to just load the latest message's position by default I think.
|
||||
const latestId = latestMessageIdentifiers[room]?.latestIdentifier;
|
||||
const latestId = latestMessageIdentifiers[room]?.latestIdentifier
|
||||
if (latestId) {
|
||||
const page = await findMessagePage(room, latestId, 10)
|
||||
currentPage = page;
|
||||
@@ -360,8 +362,8 @@ const loadRoomContent = async (room) => {
|
||||
} else{
|
||||
await loadMessagesFromQDN(room, currentPage)
|
||||
}
|
||||
;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// Initialize Quill editor //TODO check the updated editor init code
|
||||
// const initializeQuillEditor = () => {
|
||||
@@ -385,11 +387,11 @@ const loadRoomContent = async (room) => {
|
||||
|
||||
|
||||
const initializeQuillEditor = () => {
|
||||
const editorContainer = document.querySelector('#editor');
|
||||
const editorContainer = document.querySelector('#editor')
|
||||
|
||||
if (!editorContainer) {
|
||||
console.error("Editor container not found!");
|
||||
return;
|
||||
console.error("Editor container not found!")
|
||||
return
|
||||
}
|
||||
|
||||
new Quill('#editor', {
|
||||
@@ -409,7 +411,7 @@ new Quill('#editor', {
|
||||
['clean']
|
||||
]
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -418,66 +420,66 @@ new Quill('#editor', {
|
||||
const setupModalHandlers = () => {
|
||||
document.addEventListener("click", (event) => {
|
||||
if (event.target.classList.contains("inline-image")) {
|
||||
const modal = document.getElementById("image-modal");
|
||||
const modalImage = document.getElementById("modal-image");
|
||||
const caption = document.getElementById("caption");
|
||||
const modal = document.getElementById("image-modal")
|
||||
const modalImage = document.getElementById("modal-image")
|
||||
const caption = document.getElementById("caption")
|
||||
|
||||
modalImage.src = event.target.src;
|
||||
caption.textContent = event.target.alt;
|
||||
modal.style.display = "block";
|
||||
modalImage.src = event.target.src
|
||||
caption.textContent = event.target.alt
|
||||
modal.style.display = "block"
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
document.getElementById("close-modal").addEventListener("click", () => {
|
||||
document.getElementById("image-modal").style.display = "none";
|
||||
});
|
||||
document.getElementById("image-modal").style.display = "none"
|
||||
})
|
||||
|
||||
window.addEventListener("click", (event) => {
|
||||
const modal = document.getElementById("image-modal");
|
||||
const modal = document.getElementById("image-modal")
|
||||
if (event.target === modal) {
|
||||
modal.style.display = "none";
|
||||
modal.style.display = "none"
|
||||
}
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
let selectedImages = [];
|
||||
let selectedFiles = [];
|
||||
let multiResource = [];
|
||||
let attachmentIdentifiers = [];
|
||||
let selectedImages = []
|
||||
let selectedFiles = []
|
||||
let multiResource = []
|
||||
let attachmentIdentifiers = []
|
||||
|
||||
// Set up file input handling
|
||||
const setupFileInputs = (room) => {
|
||||
const imageFileInput = document.getElementById('image-input');
|
||||
const previewContainer = document.getElementById('preview-container');
|
||||
const addToPublishButton = document.getElementById('add-images-to-publish-button');
|
||||
const fileInput = document.getElementById('file-input');
|
||||
const sendButton = document.getElementById('send-button');
|
||||
const imageFileInput = document.getElementById('image-input')
|
||||
const previewContainer = document.getElementById('preview-container')
|
||||
const addToPublishButton = document.getElementById('add-images-to-publish-button')
|
||||
const fileInput = document.getElementById('file-input')
|
||||
const sendButton = document.getElementById('send-button')
|
||||
|
||||
const attachmentID = generateAttachmentID(room);
|
||||
const attachmentID = generateAttachmentID(room)
|
||||
|
||||
imageFileInput.addEventListener('change', (event) => {
|
||||
previewContainer.innerHTML = '';
|
||||
selectedImages = [...event.target.files];
|
||||
previewContainer.innerHTML = ''
|
||||
selectedImages = [...event.target.files]
|
||||
|
||||
addToPublishButton.disabled = selectedImages.length === 0;
|
||||
addToPublishButton.disabled = selectedImages.length === 0
|
||||
|
||||
selectedImages.forEach((file, index) => {
|
||||
const reader = new FileReader();
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => {
|
||||
const img = document.createElement('img');
|
||||
img.src = reader.result;
|
||||
img.alt = file.name;
|
||||
img.style = "width: 100px; height: 100px; object-fit: cover; border: 1px solid #ccc; border-radius: 5px;";
|
||||
const img = document.createElement('img')
|
||||
img.src = reader.result
|
||||
img.alt = file.name
|
||||
img.style = "width: 100px; height: 100px; object-fit: cover; border: 1px solid #ccc; border-radius: 5px;"
|
||||
|
||||
const removeButton = document.createElement('button');
|
||||
removeButton.innerText = 'Remove';
|
||||
removeButton.classList.add('remove-image-button');
|
||||
const removeButton = document.createElement('button')
|
||||
removeButton.innerText = 'Remove'
|
||||
removeButton.classList.add('remove-image-button')
|
||||
removeButton.onclick = () => {
|
||||
selectedImages.splice(index, 1);
|
||||
img.remove();
|
||||
removeButton.remove();
|
||||
addToPublishButton.disabled = selectedImages.length === 0;
|
||||
};
|
||||
selectedImages.splice(index, 1)
|
||||
img.remove()
|
||||
removeButton.remove()
|
||||
addToPublishButton.disabled = selectedImages.length === 0
|
||||
}
|
||||
|
||||
const container = document.createElement('div')
|
||||
container.style = "display: flex; flex-direction: column; align-items: center; margin: 5px;"
|
||||
@@ -650,11 +652,11 @@ function clearInputs() {
|
||||
// Show success notification
|
||||
const showSuccessNotification = () => {
|
||||
const notification = document.createElement('div')
|
||||
notification.innerText = "Message published successfully! Please wait for confirmation."
|
||||
notification.innerText = "Successfully Published! Please note that messages will not display until after they are CONFIRMED, be patient!"
|
||||
notification.style.color = "green"
|
||||
notification.style.marginTop = "1em"
|
||||
document.querySelector(".message-input-section").appendChild(notification);
|
||||
alert(`Successfully Published! Please note that messages will not display until after they are CONFIRMED, be patient!`)
|
||||
// alert(`Successfully Published! Please note that messages will not display until after they are CONFIRMED, be patient!`)
|
||||
|
||||
setTimeout(() => {
|
||||
notification.remove()
|
||||
@@ -1073,11 +1075,14 @@ const buildSingleAttachmentHtml = async (attachment, room) => {
|
||||
} else if
|
||||
(room === "admins" && attachment.mimeType && attachment.mimeType.startsWith('image/')) {
|
||||
// const imageUrl = `/arbitrary/${attachment.service}/${attachment.name}/${attachment.identifier}`;
|
||||
const decryptedBase64 = await fetchEncryptedImageBase64(attachment.service, attachment.name, attachment.identifier, attachment.mimeType)
|
||||
const dataUrl = `data:image/${attachment.mimeType};base64,${decryptedBase64}`
|
||||
// const decryptedBase64 = await fetchEncryptedImageBase64(attachment.service, attachment.name, attachment.identifier, attachment.mimeType)
|
||||
// const dataUrl = `data:image/${attachment.mimeType};base64,${decryptedBase64}`
|
||||
//<img src="${dataUrl}" alt="${attachment.filename}" class="inline-image"/>
|
||||
// above copied from removed html that is now created with fetchImageUrl TODO test this to ensure it works as expected.
|
||||
const imageHtml = await loadInLineImageHtml(attachment.service, attachment.name, attachment.identifier, attachment.filename, attachment.mimeType, 'admins')
|
||||
return `
|
||||
<div class="attachment">
|
||||
<img src="${dataUrl}" alt="${attachment.filename}" class="inline-image"/>
|
||||
${imageHtml}
|
||||
<button onclick="fetchAndSaveAttachment('${attachment.service}', '${attachment.name}', '${attachment.identifier}', '${attachment.filename}', '${attachment.mimeType}')">
|
||||
Save ${attachment.filename}
|
||||
</button>
|
||||
@@ -1160,6 +1165,63 @@ const updatePaginationControls = async (room, limit) => {
|
||||
renderPaginationControls(room, totalMessages, limit)
|
||||
}
|
||||
|
||||
const createScrollToTopButton = () => {
|
||||
if (document.getElementById('scrollToTopButton')) return
|
||||
|
||||
const button = document.createElement('button')
|
||||
button.id = 'scrollToTopButton'
|
||||
|
||||
button.innerHTML = '↑'
|
||||
|
||||
// Initial “not visible” state
|
||||
button.style.display = 'none'
|
||||
|
||||
button.style.position = 'fixed'
|
||||
button.style.bottom = '3vh'
|
||||
button.style.right = '3vw'
|
||||
button.style.width = '9vw'
|
||||
button.style.height = '9vw'
|
||||
button.style.minWidth = '45px'
|
||||
button.style.minHeight = '45px'
|
||||
button.style.maxWidth = '60px'
|
||||
button.style.maxHeight = '60px'
|
||||
button.style.borderRadius = '50%'
|
||||
button.style.backgroundColor = 'black'
|
||||
button.style.color = 'white'
|
||||
button.style.border = '2px solid white'
|
||||
button.style.boxShadow = '0 0 15px rgba(0,0,0,0.5)'
|
||||
button.style.cursor = 'pointer'
|
||||
button.style.zIndex = '1000'
|
||||
button.style.transition = 'opacity 0.3s ease, transform 0.3s ease'
|
||||
button.style.fontSize = '5vw'
|
||||
button.style.minFontSize = '18px'
|
||||
button.style.maxFontSize = '30px'
|
||||
|
||||
button.onclick = () => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
document.body.appendChild(button)
|
||||
|
||||
const adjustFontSize = () => {
|
||||
const computedStyle = window.getComputedStyle(button)
|
||||
let sizePx = parseFloat(computedStyle.fontSize)
|
||||
if (sizePx < 18) sizePx = 18
|
||||
if (sizePx > 30) sizePx = 30
|
||||
button.style.fontSize = sizePx + 'px'
|
||||
}
|
||||
adjustFontSize()
|
||||
|
||||
window.addEventListener('resize', adjustFontSize)
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
if (window.scrollY > 200) {
|
||||
button.style.display = 'block'
|
||||
} else {
|
||||
button.style.display = 'none'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Polling function to check for new messages without clearing existing ones
|
||||
|
@@ -227,6 +227,7 @@ const verifyUserIsAdmin = async () => {
|
||||
}
|
||||
|
||||
|
||||
|
||||
const verifyAddressIsAdmin = async (address) => {
|
||||
console.log('verifyAddressIsAdmin called')
|
||||
console.log('address:', address)
|
||||
@@ -857,17 +858,12 @@ const searchResourcesWithStatus = async (query, limit, status = 'local') => {
|
||||
}
|
||||
|
||||
const getResourceMetadata = async (service, name, identifier) => {
|
||||
console.log('getResourceMetadata called')
|
||||
console.log('service:', service)
|
||||
console.log('name:', name)
|
||||
console.log('identifier:', identifier)
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/arbitrary/metadata/${service}/${name}/${identifier}`, {
|
||||
method: 'GET',
|
||||
headers: { 'accept': 'application/json' }
|
||||
})
|
||||
const data = await response.json()
|
||||
console.log('Fetched resource metadata:', data)
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error('Error fetching resource metadata:', error)
|
||||
@@ -888,22 +884,35 @@ const fetchFileBase64 = async (service, name, identifier) => {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadImageHtml(service, name, identifier, filename, mimeType) {
|
||||
const loadInLineImageHtml = async (service, name, identifier, filename, mimeType, room='admins') => {
|
||||
let isEncrypted = false
|
||||
|
||||
if (room === 'admins'){
|
||||
isEncrypted = true
|
||||
}
|
||||
|
||||
if ((service === "MAIL_PRIVATE") && (room === 'admins')) {
|
||||
service = "FILE_PRIVATE"
|
||||
}
|
||||
|
||||
try {
|
||||
const url = `${baseUrl}/arbitrary/${service}/${name}/${identifier}`
|
||||
// Fetch the file as a blob
|
||||
const response = await fetch(url)
|
||||
// Convert the response to a Blob
|
||||
const fileBlob = new Blob([response], { type: mimeType })
|
||||
// Create an Object URL from the Blob
|
||||
const objectUrl = URL.createObjectURL(fileBlob)
|
||||
// Use the Object URL as the image source
|
||||
const url = `${baseUrl}/arbitrary/${service}/${name}/${identifier}?encoding=base64`
|
||||
|
||||
const response = await fetch(url,{
|
||||
method: 'GET',
|
||||
headers: { 'accept': 'text/plain' }
|
||||
})
|
||||
|
||||
const data64 = await response.text()
|
||||
const decryptedBase64 = await decryptObject(data64)
|
||||
const base64 = isEncrypted ? decryptedBase64 : data64
|
||||
const objectUrl = base64ToBlobUrl(base64, mimeType)
|
||||
const attachmentHtml = `<div class="attachment"><img src="${objectUrl}" alt="${filename}" class="inline-image"></div>`
|
||||
|
||||
return attachmentHtml
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error fetching the image:", error)
|
||||
console.error("Error loading in-line image HTML:", error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -932,18 +941,14 @@ const fetchAndSaveAttachment = async (service, name, identifier, filename, mimeT
|
||||
throw new Error(`File not found (HTTP ${response.status}): ${urlPrivate}`)
|
||||
}
|
||||
|
||||
// 2) Get the encrypted base64 text
|
||||
const encryptedBase64Data = await response.text()
|
||||
console.log("Fetched Encrypted Base64 Data:", encryptedBase64Data)
|
||||
|
||||
// 3) Decrypt => returns decrypted base64
|
||||
const decryptedBase64 = await decryptObject(encryptedBase64Data)
|
||||
console.log("Decrypted Base64 Data:", decryptedBase64)
|
||||
|
||||
// 4) Convert that to a Blob
|
||||
const fileBlob = base64ToBlob(decryptedBase64, mimeType)
|
||||
|
||||
// 5) Save the file using qortalRequest
|
||||
await qortalRequest({
|
||||
action: "SAVE_FILE",
|
||||
blob: fileBlob,
|
||||
@@ -978,7 +983,7 @@ const fetchAndSaveAttachment = async (service, name, identifier, filename, mimeT
|
||||
error
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -999,6 +1004,18 @@ const base64ToBlob = (base64String, mimeType) => {
|
||||
// Create a blob from the Uint8Array
|
||||
return new Blob([bytes], { type: mimeType })
|
||||
}
|
||||
|
||||
const base64ToBlobUrl = (base64, mimeType) => {
|
||||
const binary = atob(base64)
|
||||
const array = []
|
||||
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
array.push(binary.charCodeAt(i))
|
||||
}
|
||||
|
||||
const blob = new Blob([new Uint8Array(array)], { type: mimeType })
|
||||
return URL.createObjectURL(blob)
|
||||
}
|
||||
|
||||
|
||||
const fetchEncryptedImageBase64 = async (service, name, identifier, mimeType) => {
|
||||
@@ -1158,6 +1175,129 @@ const voteYesOnPoll = async (poll) => {
|
||||
})
|
||||
}
|
||||
|
||||
// Qortal Transaction-related calls ---------------------------------------------------------------------------
|
||||
|
||||
const processTransaction = async (rawTransaction) => {
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/transactions/process`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'text/plain',
|
||||
'X-API-VERSION': '2',
|
||||
'Content-Type': 'text/plain'
|
||||
},
|
||||
body: rawTransaction
|
||||
})
|
||||
|
||||
if (!response.ok) throw new Error(`Transaction processing failed: ${response.status}`)
|
||||
|
||||
const result = await response.text()
|
||||
console.log("Transaction successfully processed:", result)
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error("Error processing transaction:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Create a group invite transaction. This will utilize a default timeToLive (which is how long the tx will be alive, not the time until it IS live...) of 10 days in seconds, as the legacy UI has a bug that doesn't display invites older than 10 days.
|
||||
// We will also default to the MINTER group for groupId, AFTER the GROUP_APPROVAL changes, the txGroupId will need to be set for tx that require approval.
|
||||
const createGroupInviteTransaction = async (recipientAddress, adminPublicKey, groupId=694, invitee, timeToLive = 864000, txGroupId = 0) => {
|
||||
|
||||
try {
|
||||
// Fetch account reference correctly
|
||||
const accountInfo = await getAddressInfo(recipientAddress)
|
||||
const accountReference = accountInfo.reference
|
||||
|
||||
// Validate inputs before making the request
|
||||
if (!adminPublicKey || !accountReference || !recipientAddress) {
|
||||
throw new Error("Missing required parameters for group invite transaction.")
|
||||
}
|
||||
|
||||
const payload = {
|
||||
timestamp: Date.now(),
|
||||
reference: accountReference,
|
||||
fee: 0.01,
|
||||
txGroupId: txGroupId,
|
||||
recipient: recipientAddress,
|
||||
adminPublicKey: adminPublicKey,
|
||||
groupId: groupId,
|
||||
invitee: invitee || recipientAddress,
|
||||
timeToLive: timeToLive
|
||||
}
|
||||
|
||||
console.log("Sending group invite transaction payload:", payload)
|
||||
|
||||
const response = await fetch(`${baseUrl}/groups/invite`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'text/plain',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Failed to create transaction: ${response.status}, ${errorText}`)
|
||||
}
|
||||
|
||||
const rawTransaction = await response.text()
|
||||
console.log("Raw unsigned transaction created:", rawTransaction)
|
||||
return rawTransaction
|
||||
} catch (error) {
|
||||
console.error("Error creating group invite transaction:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const getLatestBlockInfo = async () => {
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/blocks/last`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch last block data: ${response.status}`);
|
||||
}
|
||||
|
||||
const blockData = await response.json();
|
||||
|
||||
// Validate and ensure the structure matches the desired format
|
||||
const formattedBlockData = {
|
||||
signature: blockData.signature || "",
|
||||
version: blockData.version || 0,
|
||||
reference: blockData.reference || "",
|
||||
transactionCount: blockData.transactionCount || 0,
|
||||
totalFees: blockData.totalFees || "0",
|
||||
transactionsSignature: blockData.transactionsSignature || "",
|
||||
height: blockData.height || 0,
|
||||
timestamp: blockData.timestamp || 0,
|
||||
minterPublicKey: blockData.minterPublicKey || "",
|
||||
minterSignature: blockData.minterSignature || "",
|
||||
atCount: blockData.atCount || 0,
|
||||
atFees: blockData.atFees || "0",
|
||||
encodedOnlineAccounts: blockData.encodedOnlineAccounts || "",
|
||||
onlineAccountsCount: blockData.onlineAccountsCount || 0,
|
||||
minterAddress: blockData.minterAddress || "",
|
||||
minterLevel: blockData.minterLevel || 0
|
||||
}
|
||||
|
||||
console.log("Last Block Data:", formattedBlockData)
|
||||
return formattedBlockData
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error fetching last block data:", error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// export {
|
||||
// userState,
|
||||
// adminGroups,
|
||||
|
Reference in New Issue
Block a user