diff --git a/assets/css/forum-styles.css b/assets/css/forum-styles.css index 556064b..73144d9 100644 --- a/assets/css/forum-styles.css +++ b/assets/css/forum-styles.css @@ -515,6 +515,16 @@ body { align-items: center; } +.promotion-section{ + display: flex; + text-align: left; + padding: 0em; + flex-wrap: wrap; + align-content: flex-start; + justify-content: center; + align-items: center; +} + /* Form Heading */ .publish-card-view h3 { @@ -726,7 +736,7 @@ body { .minter-card-results div { display: flex; - justify-content: space-between; + justify-content: center; } .minter-card-results span { diff --git a/assets/images/hashes.json b/assets/images/hashes.json deleted file mode 100644 index df2094b..0000000 --- a/assets/images/hashes.json +++ /dev/null @@ -1 +0,0 @@ -{"Vn2IvHf07wyKYZiqFbKdyA==":"image15.jpg","tr8+8zJLW6gL2hgztHPxGA==":"qortector-by-itself-back-1136x940.png","1c5eh/fGPHPcoR//H+fMFw==":"qortector-by-itself-back-1-1136x940.png","1XXCnXt4wgUqJ31QK84ILg==":"qortector-by-itself-back-2-1136x940.png","0r3mBnBwn9G6GckwOiIF9w==":"qortector-by-itself-back-3-1136x940.png","fDUJaob7KfPxKBSLvIHo7A==":"wallpaper-qortal-darkest-2000x1333.jpg","byAqpaYKeHDBS3MDCvXHNA==":"background1.jpg","ZNDFbTjNPpnwXY6SJmH+Pg==":"image7.jpg","20tXYuAvQ8cCWSfVJ29Xog==":"image9.jpg","G0BdAlJezcIP2wV6CN9Y7A==":"image1.jpg","mAQyI8jPFlEmdmzmXlZibA==":"image14.jpg","rRdnwqn5O6lQx/lniiGUNA==":"mbr-1920x1280.jpg","sRD0qybUsOSHBp21gr2THw==":"mbr-1920x1152.jpg","OD73VTiBY7I+hZSg7Qndgg==":"modded-circle-2-new-100x100.png","nDMsvlHYt+gwcsRrGOKqzw==":"chd-circle-with-letters-100x100.png","KjibQCxRAQK0AAcPVGRyqA==":"qcloud-4-1920x1080.png","eNwLDioCZHd0wl3+/CkvSw==":"mbr-1623x1112.jpg","I6N1Z4qWlbHgBNW0yZdFqA==":"mbr-1623x1082.jpg","Oy2MMqCPK1Fdznz8QkHStw==":"again-edited-qortal-minting-icon-156x156.png","RMdq6M8IxyqkmFOKAv9xlQ==":"qortal_cube_original_by-100x100.png","Uh0/Evlsn5kppPdhSgz23w==":"qortal_ui_tray_minting-32x32.png","jM4Hh1L4b8PFO5ZreHRe8w==":"coin-815x815.jpg","tLev6c8MzefvKwbPhoSYuA==":"qortal-think-tank-logo-new-multiply-test-800x800.png"} \ No newline at end of file diff --git a/assets/js/AdminBoard.js b/assets/js/AdminBoard.js index 5c0afe9..15382ec 100644 --- a/assets/js/AdminBoard.js +++ b/assets/js/AdminBoard.js @@ -1,4 +1,4 @@ -// NOTE - Change isTestMode to false prior to actual release ---- !important - You may also change identifier if you want to not show older cards. + const isEncryptedTestMode = false const encryptedCardIdentifierPrefix = "card-MAC" let isUpdateCard = false @@ -13,7 +13,7 @@ let adminPublicKeys = [] let kickTransactions = [] let banTransactions = [] let adminBoardState = { - kickedCards: new Set(), // store identifiers or addresses + kickedCards: new Set(), // store identifiers bannedCards: new Set(), // likewise hiddenList: new Set(), // user-hidden // ... we can add other things to state if needed... @@ -107,6 +107,7 @@ const loadAdminBoardPage = async () => { ` + document.body.appendChild(mainContent) const publishCardButton = document.getElementById("publish-card-button") @@ -149,13 +150,21 @@ const loadAdminBoardPage = async () => { }) } - document.getElementById('show-kicked-banned-checkbox')?.addEventListener('change', () => { - fetchAllEncryptedCards() - }) + const showKickedBannedCheckbox = document.getElementById('admin-show-kicked-banned-checkbox') - document.getElementById('show-admin-hidden-checkbox')?.addEventListener('change', () => { - fetchAllEncryptedCards() - }) + if (showKickedBannedCheckbox) { + showKickedBannedCheckbox.addEventListener('change', async (event) => { + await fetchAllEncryptedCards(true); + }) + } + + const showHiddenCardsCheckbox = document.getElementById('admin-show-hidden-checkbox') + if (showHiddenCardsCheckbox) { + showHiddenCardsCheckbox.addEventListener('change', async (event) => { + await fetchAllEncryptedCards(true) + }) + } + document.getElementById("publish-card-form").addEventListener("submit", async (event) => { event.preventDefault() @@ -172,10 +181,30 @@ const loadAdminBoardPage = async () => { } const fetchAllKicKBanTxData = async () => { - const kickTxType = "GROUP_KICK"; - const banTxType = "GROUP_BAN"; + const kickTxType = "GROUP_KICK" + const banTxType = "GROUP_BAN" - // 1) Fetch ban transactions + // Helper function to filter transactions + const filterTransactions = (rawTransactions) => { + // Group transactions by member + const memberTxMap = rawTransactions.reduce((map, tx) => { + if (!map[tx.member]) { + map[tx.member] = [] + } + map[tx.member].push(tx) + return map + }, {}) + + // Filter out members with both pending and non-pending transactions + return Object.values(memberTxMap) + .filter(txs => txs.every(tx => tx.approvalStatus !== 'PENDING')) + .flat() + // .filter((txs) => !(txs.some(tx => tx.approvalStatus === 'PENDING') && + // txs.some(tx => tx.approvalStatus !== 'PENDING'))) + // .flat() + } + + // Fetch ban transactions const rawBanTransactions = await searchTransactions({ txTypes: [banTxType], address: '', @@ -186,12 +215,13 @@ const fetchAllKicKBanTxData = async () => { startBlock: 1990000, blockLimit: 0, txGroupId: 0, - }); - // Filter out 'PENDING' - banTransactions = rawBanTransactions.filter((tx) => tx.approvalStatus !== 'PENDING'); - console.warn('banTxData (no PENDING):', banTransactions); + }) - // 2) Fetch kick transactions + // Filter transactions for bans + banTransactions = filterTransactions(rawBanTransactions) + console.warn('banTxData (filtered):', banTransactions) + + // Fetch kick transactions const rawKickTransactions = await searchTransactions({ txTypes: [kickTxType], address: '', @@ -202,12 +232,12 @@ const fetchAllKicKBanTxData = async () => { startBlock: 1990000, blockLimit: 0, txGroupId: 0, - }); - // Filter out 'PENDING' - kickTransactions = rawKickTransactions.filter((tx) => tx.approvalStatus !== 'PENDING'); - console.warn('kickTxData (no PENDING):', kickTransactions); -}; + }) + // Filter transactions for kicks + kickTransactions = filterTransactions(rawKickTransactions) + console.warn('kickTxData (filtered):', kickTransactions) +} // Example: fetch and save admin public keys and count diff --git a/assets/js/MinterBoard.js b/assets/js/MinterBoard.js index 7214594..fe7221e 100644 --- a/assets/js/MinterBoard.js +++ b/assets/js/MinterBoard.js @@ -1,11 +1,11 @@ // // NOTE - Change isTestMode to false prior to actual release ---- !important - You may also change identifier if you want to not show older cards. const testMode = false -const cardIdentifierPrefix = "Minter-board-card" +const minterCardIdentifierPrefix = "Minter-board-card" let isExistingCard = false let existingCardData = {} let existingCardIdentifier = {} const MIN_ADMIN_YES_VOTES = 9; -const GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT = 9999950 //TODO update this to correct featureTrigger height when known, either that, or pull from core. +const GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT = 2012800 //TODO update this to correct featureTrigger height when known, either that, or pull from core. let featureTriggerPassed = false let isApproved = false @@ -54,7 +54,7 @@ const loadMinterBoardPage = async () => { document.getElementById("publish-card-button").addEventListener("click", async () => { try { - const fetchedCard = await fetchExistingCard() + const fetchedCard = await fetchExistingCard(minterCardIdentifierPrefix) if (fetchedCard) { // An existing card is found if (testMode) { @@ -84,7 +84,7 @@ const loadMinterBoardPage = async () => { // Show the form const publishCardView = document.getElementById("publish-card-view") - publishCardView.style.display = "flex"; + publishCardView.style.display = "flex" document.getElementById("cards-container").style.display = "none" } catch (error) { console.error("Error checking for existing card:", error) @@ -95,7 +95,7 @@ const loadMinterBoardPage = async () => { document.getElementById("refresh-cards-button").addEventListener("click", async () => { const cardsContainer = document.getElementById("cards-container") cardsContainer.innerHTML = "
Refreshing cards...
" - await loadCards(); + await loadCards(minterCardIdentifierPrefix) }) @@ -117,17 +117,17 @@ const loadMinterBoardPage = async () => { document.getElementById("publish-card-form").addEventListener("submit", async (event) => { event.preventDefault() - await publishCard() + await publishCard(minterCardIdentifierPrefix) }) await featureTriggerCheck() - await loadCards() + await loadCards(minterCardIdentifierPrefix) } const extractMinterCardsMinterName = async (cardIdentifier) => { // Ensure the identifier starts with the prefix - if (!cardIdentifier.startsWith(`${cardIdentifierPrefix}-`)) { - throw new Error('Invalid identifier format or prefix mismatch') - } + if ((!cardIdentifier.startsWith(minterCardIdentifierPrefix)) && (!cardIdentifier.startsWith(addRemoveIdentifierPrefix))) { + throw new Error('minterCard does not match identifier check') + } // Split the identifier into parts const parts = cardIdentifier.split('-') // Ensure the format has at least 3 parts @@ -135,9 +135,22 @@ const extractMinterCardsMinterName = async (cardIdentifier) => { throw new Error('Invalid identifier format') } try { - const searchSimpleResults = await searchSimple('BLOG_POST', `${cardIdentifier}`, '', 1) - const minterName = await searchSimpleResults.name - return minterName + if (cardIdentifier.startsWith(minterCardIdentifierPrefix)){ + const searchSimpleResults = await searchSimple('BLOG_POST', `${cardIdentifier}`, '', 1) + const minterName = await searchSimpleResults.name + return minterName + } else if (cardIdentifier.startsWith(addRemoveIdentifierPrefix)) { + const searchSimpleResults = await searchSimple('BLOG_POST', `${cardIdentifier}`, '', 1) + const publisherName = searchSimpleResults.name + const cardDataResponse = await qortalRequest({ + action: "FETCH_QDN_RESOURCE", + name: publisherName, + service: "BLOG_POST", + identifier: cardIdentifier, + }) + const minterName = cardDataResponse.minterName + return minterName + } } catch (error) { throw error } @@ -146,6 +159,7 @@ const extractMinterCardsMinterName = async (cardIdentifier) => { const processMinterCards = async (validMinterCards) => { const latestCardsMap = new Map() + // Deduplicate by identifier, keeping the most recent validMinterCards.forEach(card => { const timestamp = card.updated || card.created || 0 const existingCard = latestCardsMap.get(card.identifier) @@ -155,28 +169,54 @@ const processMinterCards = async (validMinterCards) => { } }) + // Convert Map back to array + const uniqueValidCards = Array.from(latestCardsMap.values()) + const minterGroupMembers = await fetchMinterGroupMembers() const minterGroupAddresses = minterGroupMembers.map(m => m.member) const minterNameMap = new Map() - for (const card of validMinterCards) { - const minterName = await extractMinterCardsMinterName(card.identifier) + // For each card, extract minterName safely + for (const card of uniqueValidCards) { + let minterName + try { + // If this throws, we catch below and skip + minterName = await extractMinterCardsMinterName(card.identifier) + } catch (error) { + console.warn( + `Skipping card ${card.identifier} because extractMinterCardsMinterName failed:`, + error + ) + continue // Skip this card and move on + } + console.log(`minterName`, minterName) + // Next, get minterNameInfo const minterNameInfo = await getNameInfo(minterName) if (!minterNameInfo) { - console.warn(`minterNameInfo is null for minter: ${minterName}`) - continue - } - const minterAddress = await minterNameInfo.owner - - if (!minterAddress) { - console.warn(`minterAddress is FAKE or INVALID in some way! minter: ${minterName}`) - continue - } else if (minterGroupAddresses.includes(minterAddress)){ - console.log(`existing minter FOUND and/or FAKE NAME FOUND (if following is null then fake name: ${minterAddress}), not including minter card: ${card.identifier}`) + console.warn(`minterNameInfo is null for minter: ${minterName}, skipping card.`) continue } + const minterAddress = minterNameInfo.owner + // Validate the address + const addressValid = await getAddressInfo(minterAddress) + if (!minterAddress || !addressValid) { + console.warn(`minterAddress invalid or missing for: ${minterName}, skipping card.`, minterAddress) + continue + } + + // If this is a 'regular' minter card, skip if user is already a minter + if (!card.identifier.includes('QM-AR-card')) { + if (minterGroupAddresses.includes(minterAddress)) { + console.log( + `existing minter found or fake name detected. Not including minter card: ${card.identifier}` + ) + continue + } + } + + // Keep only the most recent card for each minterName const existingCard = minterNameMap.get(minterName) const cardTimestamp = card.updated || card.created || 0 const existingTimestamp = existingCard?.updated || existingCard?.created || 0 @@ -186,6 +226,7 @@ const processMinterCards = async (validMinterCards) => { } } + // Convert minterNameMap to final array const finalCards = [] const seenMinterNames = new Set() @@ -196,6 +237,7 @@ const processMinterCards = async (validMinterCards) => { } } + // Sort by timestamp descending finalCards.sort((a, b) => { const timestampA = a.updated || a.created || 0 const timestampB = b.updated || b.created || 0 @@ -205,35 +247,37 @@ const processMinterCards = async (validMinterCards) => { return finalCards } + //Main function to load the Minter Cards ---------------------------------------- -const loadCards = async () => { +const loadCards = async (cardIdentifierPrefix) => { const cardsContainer = document.getElementById("cards-container") + let isARBoard = false cardsContainer.innerHTML = "Loading cards...
" + if ((cardIdentifierPrefix.startsWith(`QM-AR-card`))) { + isARBoard = true + console.warn(`ARBoard determined:`, isARBoard) + } try { - const response = await searchSimple('BLOG_POST', `${cardIdentifierPrefix}`, '' , 0) if (!response || !Array.isArray(response) || response.length === 0) { cardsContainer.innerHTML = "No cards found.
" - return; + return } - // Validate cards and filter const validatedCards = await Promise.all( response.map(async card => { const isValid = await validateCardStructure(card) return isValid ? card : null }) - ); - + ) const validCards = validatedCards.filter(card => card !== null) if (validCards.length === 0) { cardsContainer.innerHTML = "No valid cards found.
" return } - const finalCards = await processMinterCards(validCards) // Display skeleton cards immediately @@ -264,7 +308,6 @@ const loadCards = async () => { removeSkeleton(card.identifier) return } - const pollPublisherPublicKey = await getPollPublisherPublicKey(cardDataResponse.poll) const cardPublisherPublicKey = await getPublicKeyByName(card.name) @@ -273,14 +316,39 @@ const loadCards = async () => { removeSkeleton(card.identifier) return } - const pollResults = await fetchPollResults(cardDataResponse.poll) const bgColor = generateDarkPastelBackgroundBy(card.name) const commentCount = await countComments(card.identifier) const cardUpdatedTime = card.updated || null - const finalCardHTML = await createCardHTML(cardDataResponse, pollResults, card.identifier, commentCount, cardUpdatedTime, bgColor) - + + if (isARBoard) { + const name = await getNameInfo(cardDataResponse.minterName) + const address = name.owner + if (minterAdminAddresses && minterGroupAddresses) { + if (!minterAdminAddresses.includes(address) && !minterGroupAddresses.includes(address)) { + console.warn(`Found card from ARBoard that contained a non-minter!`) + removeSkeleton(card.identifier) + return + } + } else if (!minterAdminAddresses || !minterGroupAddresses){ + const minterGroup = await fetchMinterGroupMembers() + const adminGroup = await fetchMinterGroupAdmins() + minterAdminAddresses = adminGroup.map(m => m.member) + minterGroupAddresses = minterGroup.map(m => m.member) + if (!minterAdminAddresses.includes(address) && !minterGroupAddresses.includes(address)) { + console.warn(`Found card from ARBoard that contained a non-minter!`) + removeSkeleton(card.identifier) + return + } + } + } + + const finalCardHTML = isARBoard ? // If we're calling from the ARBoard, we will create HTML with a different function. + await createARCardHTML(cardDataResponse, pollResults, card.identifier, commentCount, cardUpdatedTime, bgColor) + : + await createCardHTML(cardDataResponse, pollResults, card.identifier, commentCount, cardUpdatedTime, bgColor) replaceSkeleton(card.identifier, finalCardHTML) + } catch (error) { console.error(`Error processing card ${card.identifier}:`, error) removeSkeleton(card.identifier) @@ -326,7 +394,7 @@ const createSkeletonCardHTML = (cardIdentifier) => { } // Function to check and fech an existing Minter Card if attempting to publish twice ---------------------------------------- -const fetchExistingCard = async () => { +const fetchExistingCard = async (cardIdentifierPrefix) => { try { const response = await searchSimple('BLOG_POST', `${cardIdentifierPrefix}`, `${userState.accountName}`, 0, 0, '', true) @@ -344,7 +412,6 @@ const fetchExistingCard = async () => { service: "BLOG_POST", identifier: mostRecentCard.identifier }) - existingCardIdentifier = mostRecentCard.identifier existingCardData = cardDataResponse isExistingCard = true @@ -418,7 +485,7 @@ const loadCardIntoForm = async (cardData) => { } // Main function to publish a new Minter Card ----------------------------------------------- -const publishCard = async () => { +const publishCard = async (cardIdentifierPrefix) => { const minterGroupData = await fetchMinterGroupMembers() const minterGroupAddresses = minterGroupData.map(m => m.member) @@ -486,7 +553,7 @@ const publishCard = async () => { document.getElementById("publish-card-form").reset() document.getElementById("publish-card-view").style.display = "none" document.getElementById("cards-container").style.display = "flex" - await loadCards() + await loadCards(minterCardIdentifierPrefix) } catch (error) { @@ -1084,44 +1151,75 @@ const featureTriggerCheck = async () => { } const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) => { + + if (!userState.isMinterAdmin){ + console.warn(`User is NOT an admin, not displaying invite/approve button...`) + return null + } + const isBlockPassed = await featureTriggerCheck() - let minAdminCount const minterAdmins = await fetchMinterGroupAdmins() - if (!isBlockPassed){ - console.warn(`feature trigger not passed, using static number for minAdminCount`) - minAdminCount = 9 - } - - if ((minterAdmins) && (minterAdmins.length === 1)){ - console.warn(`simply a double-check that there is only one MINTER group admin, in which case the group hasn't been transferred to null...keeping default minAdminCount of: ${minAdminCount}`) - } else if ((minterAdmins) && (minterAdmins.length > 1) && isBlockPassed){ - const totalAdmins = minterAdmins.length - const fortyPercent = totalAdmins * 0.40 - minAdminCount = Math.round(fortyPercent) - console.warn(`this is another check to ensure minterAdmin group has more than 1 admin. IF so we will calculate the 40% needed for GROUP_APPROVAL, that number is: ${minAdminCount}`) - } - + let minAdminCount = 9 if (isBlockPassed) { - const minterNameInfo = await getNameInfo(creator) - const minterAddress = await minterNameInfo.owner - if (userState.isMinterAdmin){ - let groupApprovalHtml = await checkGroupApprovalAndCreateButton(minterAddress, cardIdentifier, "GROUP_INVITE") - if (groupApprovalHtml) { - return groupApprovalHtml - } - }else{ - console.log(`USER NOT ADMIN, no need for group approval buttons...`) + minAdminCount = Math.round(minterAdmins.length * 0.4) + console.warn(`Using 40% => ${minAdminCount}`) + } + + if (adminYes < minAdminCount) { + console.warn(`Admin votes not high enough (have=${adminYes}, need=${minAdminCount}). No button.`) + return null + } + console.log(`passed initial button creation checks, pulling additional data...`) + + const minterNameInfo = await getNameInfo(creator) + const minterAddress = await minterNameInfo.owner + + const previousBanTx = await searchTransactions({ + txTypes: ['GROUP_BAN'], + address: `${minterAddress}`, + confirmationStatus: 'CONFIRMED', + limit: 0, + reverse: true, + offset: 0, + startBlock: 1990000, + blockLimit: 0, + txGroupId: 0, + }) + const previousBan = previousBanTx.filter((tx) => tx.approvalStatus !== 'PENDING') + const previousKickTx = await searchTransactions({ + txTypes: ['GROUP_KICK'], + address: `${minterAddress}`, + confirmationStatus: 'CONFIRMED', + limit: 0, + reverse: true, + offset: 0, + startBlock: 1990000, + blockLimit: 0, + txGroupId: 0, + }) + const previousKick = previousKickTx.filter((tx) => tx.approvalStatus !== 'PENDING') + const priorBanOrKick = (previousKick.length > 0 || previousBan.length > 0) + + console.warn(`PriorBanOrKick determination:`, priorBanOrKick) + + const inviteButtonHtml = createInviteButtonHtml(creator, cardIdentifier) + const groupApprovalHtml = await checkGroupApprovalAndCreateButton(minterAddress, cardIdentifier, "GROUP_INVITE") + + if (!priorBanOrKick) { + console.log(`No prior kick/ban found, creating invite (or approve) button...` ) + console.warn(`Existing Numbers - adminYes/minAdminCount: ${adminYes}/${minAdminCount}`) + if (groupApprovalHtml){ + console.warn(`groupApprovalCheck found existing groupApproval, returning approval button instead of invite button...`) + return groupApprovalHtml } - } - - if (adminYes >= minAdminCount && (userState.isMinterAdmin)) { - const inviteButtonHtml = createInviteButtonHtml(creator, cardIdentifier) - console.log(`admin votes over 9, creating invite button...`, adminYes) + console.warn(`No pending approvals or prior kick/ban found, but votes are high enough, returning invite button...`) return inviteButtonHtml - } - return null + } else if (priorBanOrKick){ + console.warn(`Prior kick/ban found! Including BOTH buttons (due to complexities in checking, displaying both buttons is simpler than attempting to display only one)...`) + return inviteButtonHtml + groupApprovalHtml + } } const findPendingApprovalTxForAddress = async (address, txType, limit = 0, offset = 0) => { @@ -1132,17 +1230,12 @@ const findPendingApprovalTxForAddress = async (address, txType, limit = 0, offse if (txType) { relevantTypes = new Set([txType]) } else { - relevantTypes = new Set(["GROUP_INVITE", "GROUP_BAN", "GROUP_KICK"]) + relevantTypes = new Set(["GROUP_INVITE", "GROUP_BAN", "GROUP_KICK", "ADD_GROUP_ADMIN", "REMOVE_GROUP_ADMIN"]) } // Filter pending TX for relevant types const relevantTxs = pendingTxs.filter((tx) => relevantTypes.has(tx.type)) - // Further filter by whether 'address' matches the correct field - // - GROUP_INVITE => invitee - // - GROUP_BAN => offender - // - GROUP_KICK => member - // If the user passed a specific txType, only one branch might matter. const matchedTxs = relevantTxs.filter((tx) => { switch (tx.type) { case "GROUP_INVITE": @@ -1151,33 +1244,22 @@ const findPendingApprovalTxForAddress = async (address, txType, limit = 0, offse return tx.offender === address case "GROUP_KICK": return tx.member === address + case "ADD_GROUP_ADMIN": + return tx.member === address + case "REMOVE_GROUP_ADMIN": + return tx.admin === address default: return false } }) + console.warn(`matchedTxs:`,matchedTxs) return matchedTxs // Array of matching pending transactions } const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transactionType) => { - const txTypes = [transactionType] - - const txSearchResults = await searchTransactions({ - txTypes, - address: `${address}`, - confirmationStatus: 'CONFIRMED', - limit: 0, - reverse: true, - offset: 0, - startBlock: 1990000, - blockLimit: 0, - txGroupId: 694 - }) - - const approvalTxType = ['GROUP_APPROVAL'] const approvalSearchResults = await searchTransactions({ - txTypes: approvalTxType, - address: `${address}`, + txTypes: ['GROUP_APPROVAL'], confirmationStatus: 'CONFIRMED', limit: 0, reverse: true, @@ -1186,73 +1268,246 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa blockLimit: 0, txGroupId: 0 }) + const pendingApprovals = await findPendingApprovalTxForAddress(address, transactionType); - console.warn(`transaction search results, this is for comparison to pendingApprovals search, these are not used:`,txSearchResults) - const pendingApprovals = await findPendingApprovalTxForAddress(address, transactionType) - - if (pendingApprovals) { - console.warn(`this is what is used for pending results... pendingApprovals FOUND:`, pendingApprovals) + // If no pending transaction found, return null + if (!pendingApprovals || pendingApprovals.length === 0) { + console.warn("no pending approval transactions found, returning null...") + return null; } - - if ((pendingApprovals.length === 0) || (!pendingApprovals)) { - console.warn(`no pending approval transactions found, returning null...`) - return null - } - const existingApprovalCount = approvalSearchResults.length const txSig = pendingApprovals[0].signature + // Among the already-confirmed GROUP_APPROVAL, filter for those referencing this txSig + const relevantApprovals = approvalSearchResults.filter( + (approvalTx) => approvalTx.pendingSignature === txSig + ) + const { tableHtml, uniqueApprovalCount } = await buildApprovalTableHtml( + relevantApprovals, + getNameFromAddress + ) - if (transactionType === `GROUP_INVITE`){ - + if (transactionType === "GROUP_INVITE") { const approvalButtonHtml = ` - ++ Existing ${transactionType} Approvals: ${uniqueApprovalCount} +
+ ${tableHtml} + ++ Existing ${transactionType} Approvals: ${uniqueApprovalCount} +
+ ${tableHtml} + ++ Existing ${transactionType} Approvals: ${uniqueApprovalCount} +
+ ${tableHtml} + ++ Existing ${transactionType} Approvals: ${uniqueApprovalCount} +
+ ${tableHtml} + ++ Existing ${transactionType} Approvals: ${uniqueApprovalCount} +
+ ${tableHtml} + +Admin Name | +Approval Time | +
---|
+ v1.0b information coming soon.- All Features related to new featureTriggers is completed and tested. +
This is the innitial 'alpha' of the Mintership Forum / Mintership tools that will be built into the final Q-Mintership app. This is a simplistic version built by crowetic that will offer a very simple communciations location, and the tools for the minter admins to accomplish the necessary GROUP_APPROVAL transactions. Scroll down for the currently available tools...
\n \n\n Learn more about the Mintership concept, and why it was needed. The days of 'sponsorship' are a thing of the past on the Qortal Network. No more will there be the ability to self-sponsor. A new era of Qortal begins!
\n\n Not already minting? You've come to the right place to get started. Check the Mintership Forum link for more information. The updated Q-Mintership app will be a more fully featured application.
\n\n Are you one of the initially selected Minter Admins? We have the tools here you need to create and approve GROUP_APPROVAL transactions, and communicate securely with your fellow admins.
\n\n Would you like to become a minter? You've come to the right place! Obtain details about the new Mintership-based minting process on Qortal.
\n\n If you are a Minter Admin, you will need to know how to create and sign GROUP_APPROVAL transactions. You may do so here!
\n\n Every mission has a beginning. If your mission is to become a minter, or a Minter Admin, then you've landed at the correct launchpad!
\n\n This is the very start of the Q-Mintership app. It will be dramatically changing upon the beta release, and modification to the Q-Mintership Q-App. This initial version is a version that could be launched more quickly, and does not have nearly as much functionality as what will exist once the main app goes live.
\n