main #4
@ -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 {
|
||||
|
@ -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"}
|
@ -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 () => {
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
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
|
||||
|
@ -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 = "<p>Refreshing cards...</p>"
|
||||
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 = "<p>Loading cards...</p>"
|
||||
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 = "<p>No cards found.</p>"
|
||||
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 = "<p>No valid cards found.</p>"
|
||||
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 = `
|
||||
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||
<h2 style="color:rgb(181, 214, 100);">Existing Invite Approvals: ${existingApprovalCount}</h2>
|
||||
<button
|
||||
style="padding: 8px; background:rgb(37, 97, 99); color:rgb(215, 215, 215) ; border: 1px solid #333; border-color: white; border-radius: 5px; cursor: pointer;"
|
||||
onmouseover="this.style.backgroundColor='rgb(25, 47, 39) '"
|
||||
onmouseout="this.style.backgroundColor='rgb(37, 96, 99) '"
|
||||
onclick="handleGroupApproval('${txSig}')">
|
||||
Approve Invite Tx
|
||||
</button>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||
<p style="color: rgb(181, 214, 100);">
|
||||
Existing ${transactionType} Approvals: ${uniqueApprovalCount}
|
||||
</p>
|
||||
${tableHtml}
|
||||
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||
<button
|
||||
style="
|
||||
padding: 8px;
|
||||
background: rgb(37, 97, 99);
|
||||
color: rgb(215, 215, 215);
|
||||
border: 1px solid #333;
|
||||
border-color: white;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
"
|
||||
onmouseover="this.style.backgroundColor='rgb(25, 47, 39)'"
|
||||
onmouseout="this.style.backgroundColor='rgb(37, 96, 99)'"
|
||||
onclick="handleGroupApproval('${txSig}')"
|
||||
>
|
||||
Approve Invite Tx
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
return approvalButtonHtml
|
||||
}
|
||||
|
||||
if (transactionType === `GROUP_KICK`){
|
||||
|
||||
|
||||
if (transactionType === "GROUP_KICK") {
|
||||
const approvalButtonHtml = `
|
||||
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||
<h2 style="color:rgb(199, 100, 64);">Existing Kick Approvals: ${existingApprovalCount}</h2>
|
||||
<button
|
||||
style="padding: 8px; background:rgb(119, 91, 21); color:rgb(201, 255, 251) ; border: 1px solid #333; border-color:rgb(102, 69, 60); border-radius: 5px; cursor: pointer;"
|
||||
onmouseover="this.style.backgroundColor='rgb(50, 52, 51) '"
|
||||
onmouseout="this.style.backgroundColor='rgb(119, 91, 21) '"
|
||||
onclick="handleGroupApproval('${txSig}')">
|
||||
Approve Kick Tx
|
||||
</button>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||
<p style="color: rgb(199, 100, 64);">
|
||||
Existing ${transactionType} Approvals: ${uniqueApprovalCount}
|
||||
</p>
|
||||
${tableHtml}
|
||||
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||
<button
|
||||
style="
|
||||
padding: 8px;
|
||||
background: rgb(119, 91, 21);
|
||||
color: rgb(201, 255, 251);
|
||||
border: 1px solid #333;
|
||||
border-color: rgb(102, 69, 60);
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
"
|
||||
onmouseover="this.style.backgroundColor='rgb(50, 52, 51)'"
|
||||
onmouseout="this.style.backgroundColor='rgb(119, 91, 21)'"
|
||||
onclick="handleGroupApproval('${txSig}')"
|
||||
>
|
||||
Approve Kick Tx
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
return approvalButtonHtml
|
||||
}
|
||||
|
||||
if (transactionType === `GROUP_BAN`){
|
||||
|
||||
|
||||
if (transactionType === "GROUP_BAN") {
|
||||
const approvalButtonHtml = `
|
||||
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||
<h2 style="color:rgb(189, 40, 40);">Existing Ban Approvals: ${existingApprovalCount}</h2>
|
||||
<button
|
||||
style="padding: 8px; background:rgb(54, 7, 7); color:rgb(201, 255, 251) ; border: 1px solid #333; border-color:rgb(204, 94, 94); border-radius: 5px; cursor: pointer;"
|
||||
onmouseover="this.style.backgroundColor='rgb(50, 52, 51) '"
|
||||
onmouseout="this.style.backgroundColor='rgb(54, 7, 7) '"
|
||||
onclick="handleGroupApproval('${txSig}')">
|
||||
Approve Ban Tx
|
||||
</button>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||
<p style="color: rgb(189, 40, 40);">
|
||||
Existing ${transactionType} Approvals: ${uniqueApprovalCount}
|
||||
</p>
|
||||
${tableHtml}
|
||||
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||
<button
|
||||
style="
|
||||
padding: 8px;
|
||||
background: rgb(54, 7, 7);
|
||||
color: rgb(201, 255, 251);
|
||||
border: 1px solid #333;
|
||||
border-color: rgb(204, 94, 94);
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
"
|
||||
onmouseover="this.style.backgroundColor='rgb(50, 52, 51)'"
|
||||
onmouseout="this.style.backgroundColor='rgb(54, 7, 7)'"
|
||||
onclick="handleGroupApproval('${txSig}')"
|
||||
>
|
||||
Approve Ban Tx
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
return approvalButtonHtml
|
||||
}
|
||||
|
||||
if (transactionType === "ADD_GROUP_ADMIN") {
|
||||
const approvalButtonHtml = `
|
||||
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||
<p style="color: rgb(40, 144, 189);">
|
||||
Existing ${transactionType} Approvals: ${uniqueApprovalCount}
|
||||
</p>
|
||||
${tableHtml}
|
||||
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||
<button
|
||||
style="
|
||||
padding: 8px;
|
||||
background: rgb(8, 71, 69);
|
||||
color: rgb(201, 255, 251);
|
||||
border: 1px solid #333;
|
||||
border-color: rgb(198, 252, 249);
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
"
|
||||
onmouseover="this.style.backgroundColor='rgb(17, 41, 29)'"
|
||||
onmouseout="this.style.backgroundColor='rgb(8, 71, 69)'"
|
||||
onclick="handleGroupApproval('${txSig}')"
|
||||
>
|
||||
Approve Add-Admin Tx
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
return approvalButtonHtml
|
||||
}
|
||||
|
||||
if (transactionType === "REMOVE_GROUP_ADMIN") {
|
||||
const approvalButtonHtml = `
|
||||
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||
<p style="color: rgb(189, 40, 40);">
|
||||
Existing ${transactionType} Approvals: ${uniqueApprovalCount}
|
||||
</p>
|
||||
${tableHtml}
|
||||
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||
<button
|
||||
style="
|
||||
padding: 8px;
|
||||
background: rgb(54, 7, 7);
|
||||
color: rgb(201, 255, 251);
|
||||
border: 1px solid #333;
|
||||
border-color: rgb(204, 94, 94);
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
"
|
||||
onmouseover="this.style.backgroundColor='rgb(50, 52, 51)'"
|
||||
onmouseout="this.style.backgroundColor='rgb(54, 7, 7)'"
|
||||
onclick="handleGroupApproval('${txSig}')"
|
||||
>
|
||||
Approve Remove-Admin Tx
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
return approvalButtonHtml
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function buildApprovalTableHtml(approvalTxs, getNameFunc) {
|
||||
// Build a Map of adminAddress => one transaction (to handle multiple approvals from same admin)
|
||||
const approvalMap = new Map()
|
||||
for (const tx of approvalTxs) {
|
||||
const adminAddr = tx.creatorAddress
|
||||
if (!approvalMap.has(adminAddr)) {
|
||||
approvalMap.set(adminAddr, tx)
|
||||
}
|
||||
}
|
||||
|
||||
// Turn the map into an array for iteration
|
||||
const approvalArray = Array.from(approvalMap, ([adminAddr, tx]) => ({ adminAddr, tx }))
|
||||
|
||||
// Build table rows asynchronously, since we need getNameFromAddress
|
||||
const tableRows = await Promise.all(
|
||||
approvalArray.map(async ({ adminAddr, tx }) => {
|
||||
let adminName
|
||||
try {
|
||||
adminName = await getNameFunc(adminAddr)
|
||||
} catch (err) {
|
||||
console.warn(`Error fetching name for ${adminAddr}:`, err)
|
||||
adminName = null
|
||||
}
|
||||
|
||||
const displayName =
|
||||
adminName && adminName !== adminAddr
|
||||
? adminName
|
||||
: "(No registered name)"
|
||||
|
||||
// Format the transaction timestamp
|
||||
const dateStr = new Date(tx.timestamp).toLocaleString()
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td style="border: 1px solid rgb(255, 255, 255); padding: 4px; color: #234565">${displayName}</td>
|
||||
<td style="border: 1px solid rgb(255, 254, 254); padding: 4px;">${dateStr}</td>
|
||||
</tr>
|
||||
`
|
||||
})
|
||||
)
|
||||
|
||||
// The total unique approvals = number of entries in approvalMap
|
||||
const uniqueApprovalCount = approvalMap.size;
|
||||
|
||||
// 4) Wrap the table in a container with horizontal scroll:
|
||||
// 1) max-width: 100% makes it fit the parent (card) width
|
||||
// 2) overflow-x: auto allows scrolling if the table is too wide
|
||||
const containerHtml = `
|
||||
<div style="max-width: 100%; overflow-x: auto;">
|
||||
<table style="border: 1px solid #ccc; border-collapse: collapse; width: 100%;">
|
||||
<thead>
|
||||
<tr style="background:rgba(6, 50, 59, 0.61);">
|
||||
<th style="border: 1px solid #ffffff; padding: 4px;">Admin Name</th>
|
||||
<th style="border: 1px solid #ffffff; padding: 4px;">Approval Time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${tableRows.join("")}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`
|
||||
|
||||
// Return both the container-wrapped table and the count of unique approvals
|
||||
return {
|
||||
tableHtml: containerHtml,
|
||||
uniqueApprovalCount
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const handleGroupApproval = async (pendingSignature) => {
|
||||
try{
|
||||
if (!userState.isMinterAdmin) {
|
||||
|
@ -56,7 +56,7 @@ function loadMessagesFromLocalStorage() {
|
||||
console.log(`Loaded ${messageOrder.length} messages from localStorage.`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading messages from localStorage:", error);
|
||||
console.error("Error loading messages from localStorage:", error)
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,6 +98,23 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
})
|
||||
})
|
||||
|
||||
const addRemoveAdminLinks = document.querySelectorAll('a[href="ADDREMOVEADMIN"]')
|
||||
addRemoveAdminLinks.forEach(link => {
|
||||
link.addEventListener('click', async (event) => {
|
||||
event.preventDefault()
|
||||
// Possibly require user to login if not logged
|
||||
if (!userState.isLoggedIn) {
|
||||
await login()
|
||||
}
|
||||
if (typeof loadMinterBoardPage === "undefined") {
|
||||
console.log("loadMinterBoardPage not found, loading script dynamically...")
|
||||
await loadScript("./assets/js/MinterBoard.js")
|
||||
}
|
||||
await loadAddRemoveAdminPage()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
// --- ADMIN CHECK ---
|
||||
await verifyUserIsAdmin()
|
||||
|
||||
@ -116,7 +133,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
}
|
||||
}
|
||||
|
||||
if (userState.isAdmin) {
|
||||
if (userState.isAdmin || userState.isForumAdmin || userState.isMinterAdmin) {
|
||||
console.log(`User is an Admin. Admin-specific buttons will remain visible.`)
|
||||
|
||||
// DATA-BOARD Links for Admins
|
||||
@ -233,7 +250,7 @@ const loadForumPage = async () => {
|
||||
</div>
|
||||
`
|
||||
|
||||
document.body.appendChild(mainContent);
|
||||
document.body.appendChild(mainContent)
|
||||
|
||||
// Add event listeners to room buttons
|
||||
document.getElementById("minters-room").addEventListener("click", () => {
|
||||
|
@ -4,6 +4,7 @@ let adminGroupIDs = ["721", "1", "673"]
|
||||
// Settings to allow non-devmode development with 'live-server' module
|
||||
let baseUrl = ''
|
||||
let isOutsideOfUiDevelopment = false
|
||||
let nullAddress = 'QdSnUy6sUiEnaN87dWmE92g1uQjrvPgrWG'
|
||||
|
||||
if (typeof qortalRequest === 'function') {
|
||||
console.log('qortalRequest is available as a function. Setting development mode to false and baseUrl to nothing.')
|
||||
@ -194,6 +195,13 @@ const userState = {
|
||||
isForumAdmin: false
|
||||
}
|
||||
|
||||
const validateQortalAddress = async (address) => {
|
||||
// Regular expression to match Qortal addresses
|
||||
const qortalAddressRegex = /^Q[a-zA-Z0-9]{32}$/
|
||||
// Test the address against the regex
|
||||
return qortalAddressRegex.test(address)
|
||||
}
|
||||
|
||||
// USER-RELATED QORTAL CALLS ------------------------------------------
|
||||
// Obtain the address of the authenticated user checking userState.accountAddress first.
|
||||
const getUserAddress = async () => {
|
||||
@ -1458,6 +1466,120 @@ const createGroupKickTransaction = async (recipientAddress, adminPublicKey, grou
|
||||
}
|
||||
}
|
||||
|
||||
const createAddGroupAdminTransaction = async (ownerPublicKey, groupId=694, member, txGroupId, fee) => {
|
||||
// If utilized to create a GROUP_APPROVAL tx, for MINTER group, then 'txCreatorPublicKey' takes the place of 'ownerPublicKey', and 'txGroupId' is required. Otherwise, txGroupId is 0 and ownerPublicKey is the tx creator, as creator = owner.
|
||||
try {
|
||||
|
||||
let reference
|
||||
|
||||
if (!ownerPublicKey){
|
||||
console.warn(`ownerPublicKey not passed, obtaining user public key...`)
|
||||
const info = await getAddressInfo(userState.accountAddress)
|
||||
reference = info.reference
|
||||
ownerPublicKey = info.publicKey
|
||||
}else {
|
||||
// Fetch account reference correctly
|
||||
const addr = await getAddressFromPublicKey(ownerPublicKey)
|
||||
const accountInfo = await getAddressInfo(addr)
|
||||
reference = accountInfo.reference
|
||||
}
|
||||
|
||||
// Validate inputs before making the request
|
||||
if (!ownerPublicKey || !reference) {
|
||||
throw new Error("Missing required parameters for group invite transaction.")
|
||||
}
|
||||
|
||||
const payload = {
|
||||
timestamp: Date.now(),
|
||||
reference,
|
||||
fee,
|
||||
txGroupId,
|
||||
ownerPublicKey,
|
||||
groupId,
|
||||
member
|
||||
}
|
||||
console.log("Sending ADD_GROUP_ADMIN transaction payload:", payload)
|
||||
const response = await fetch(`${baseUrl}/groups/addadmin`, {
|
||||
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 ADD_GROUP_ADMIN transaction:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const createRemoveGroupAdminTransaction = async (ownerPublicKey, groupId=694, admin, txGroupId, fee) => {
|
||||
console.log(`removeGroupAdminTxCreationInfo:`,ownerPublicKey, groupId, fee, txGroupId, admin)
|
||||
|
||||
try {
|
||||
let reference
|
||||
|
||||
if (!ownerPublicKey){
|
||||
console.warn(`ownerPublicKey not passed, obtaining user public key...`)
|
||||
const info = getAddressInfo(userState.accountAddress)
|
||||
reference = info.reference
|
||||
ownerPublicKey = info.publicKey
|
||||
} else {
|
||||
// Fetch account reference correctly
|
||||
const addr = await getAddressFromPublicKey(ownerPublicKey)
|
||||
const accountInfo = await getAddressInfo(addr)
|
||||
reference = accountInfo.reference
|
||||
console.warn(`reference for removeTx:`, reference)
|
||||
console.warn(`ownerPublicKey for removeTx`, ownerPublicKey)
|
||||
}
|
||||
|
||||
// Validate inputs before making the request
|
||||
if (!ownerPublicKey || !reference) {
|
||||
throw new Error("Missing required parameters for transaction.")
|
||||
}
|
||||
|
||||
const payload = {
|
||||
timestamp: Date.now(),
|
||||
reference,
|
||||
fee,
|
||||
txGroupId,
|
||||
ownerPublicKey,
|
||||
groupId,
|
||||
admin,
|
||||
}
|
||||
console.log("Sending REMOVE_GROUP_ADMINtransaction payload:", payload)
|
||||
const response = await fetch(`${baseUrl}/groups/removeadmin`, {
|
||||
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 REMOVE_GROUP_ADMIN transaction:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const createGroupApprovalTransaction = async (adminPublicKey, pendingSignature, txGroupId=0, fee=0.01) => {
|
||||
|
||||
try {
|
||||
@ -1468,7 +1590,7 @@ const createGroupApprovalTransaction = async (adminPublicKey, pendingSignature,
|
||||
|
||||
// Validate inputs before making the request
|
||||
if (!adminPublicKey || !accountReference ) {
|
||||
throw new Error("Missing required parameters for group invite transaction.")
|
||||
throw new Error("Missing required parameters for transaction.")
|
||||
}
|
||||
|
||||
const payload = {
|
||||
@ -1771,33 +1893,3 @@ const searchPendingTransactions = async (limit = 20, offset = 0) => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// export {
|
||||
// userState,
|
||||
// adminGroups,
|
||||
// searchResourcesWithMetadata,
|
||||
// searchResourcesWithStatus,
|
||||
// getResourceMetadata,
|
||||
// renderData,
|
||||
// getProductDetails,
|
||||
// getUserGroups,
|
||||
// getUserAddress,
|
||||
// login,
|
||||
// timestampToHumanReadableDate,
|
||||
// base64EncodeString,
|
||||
// verifyUserIsAdmin,
|
||||
// fetchAllDataByIdentifier,
|
||||
// fetchOwnerAddressFromName,
|
||||
// verifyAddressIsAdmin,
|
||||
// uid,
|
||||
// fetchAllGroups,
|
||||
// getNameInfo,
|
||||
// publishMultipleResources,
|
||||
// getPublicKeyByName,
|
||||
// objectToBase64,
|
||||
// fetchMinterGroupAdmins
|
||||
// }
|
||||
|
107
index.html
107
index.html
@ -1,37 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html >
|
||||
<head>
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
|
||||
<link rel="shortcut icon" href="assets/images/modded-circle-2-new-128x128.png" type="image/x-icon">
|
||||
<meta name="description" content="Welcome to the Mintership Forum (alpha version)">
|
||||
|
||||
|
||||
<title>Home</title>
|
||||
<link rel="stylesheet" href="assets/web/assets/mobirise-icons2/mobirise2.css">
|
||||
<link rel="stylesheet" href="assets/web/assets/mobirise-icons/mobirise-icons.css">
|
||||
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap-grid.min.css">
|
||||
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap-reboot.min.css">
|
||||
<link rel="stylesheet" href="assets/parallax/jarallax.css">
|
||||
<link rel="stylesheet" href="assets/animatecss/animate.css">
|
||||
<link rel="stylesheet" href="assets/dropdown/css/style.css">
|
||||
<link rel="stylesheet" href="assets/socicon/css/styles.css">
|
||||
<link rel="stylesheet" href="assets/theme/css/style.css">
|
||||
<link rel="preload" as="style" href="assets/mobirise/css/mbr-additional.css?v=U9lZDZ"><link rel="stylesheet" href="assets/mobirise/css/mbr-additional.css?v=U9lZDZ" type="text/css">
|
||||
<head>
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="./assets/css/forum-styles.css">
|
||||
<link rel="preload" href="./assets/css/css.css?family=DM+Sans:100,200,300,400,500,600,700,800,900,100i,200i,300i,400i,500i,600i,700i,800i,900i&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||
<noscript><link rel="stylesheet" href="./assets/css/css.css?family=DM+Sans:100,200,300,400,500,600,700,800,900,100i,200i,300i,400i,500i,600i,700i,800i,900i&display=swap"></noscript>
|
||||
<link rel="preload" href="./assets/css/space-grotesk.css?family=Space+Grotesk:300,400,500,600,700&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||
<noscript><link rel="stylesheet" href="./assets/css/space-grotesk.css?family=Space+Grotesk:300,400,500,600,700&display=swap"></noscript>
|
||||
<link href="./assets/quill/quill.snow.css" rel="stylesheet">
|
||||
</head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
|
||||
<link rel="shortcut icon" href="assets/images/modded-circle-2-new-128x128.png" type="image/x-icon">
|
||||
<meta name="description" content="Welcome to the Mintership Forum (alpha version)">
|
||||
|
||||
|
||||
<title>Home</title>
|
||||
<link rel="stylesheet" href="assets/web/assets/mobirise-icons2/mobirise2.css">
|
||||
<link rel="stylesheet" href="assets/web/assets/mobirise-icons/mobirise-icons.css">
|
||||
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap-grid.min.css">
|
||||
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap-reboot.min.css">
|
||||
<link rel="stylesheet" href="assets/parallax/jarallax.css">
|
||||
<link rel="stylesheet" href="assets/animatecss/animate.css">
|
||||
<link rel="stylesheet" href="assets/dropdown/css/style.css">
|
||||
<link rel="stylesheet" href="assets/socicon/css/styles.css">
|
||||
<link rel="stylesheet" href="assets/theme/css/style.css">
|
||||
<link rel="preload" as="style" href="assets/mobirise/css/mbr-additional.css?v=U9lZDZ"><link rel="stylesheet" href="assets/mobirise/css/mbr-additional.css?v=U9lZDZ" type="text/css">
|
||||
<link rel="stylesheet" href="./assets/css/forum-styles.css">
|
||||
<link rel="preload" href="./assets/css/css.css?family=DM+Sans:100,200,300,400,500,600,700,800,900,100i,200i,300i,400i,500i,600i,700i,800i,900i&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||
<noscript><link rel="stylesheet" href="./assets/css/css.css?family=DM+Sans:100,200,300,400,500,600,700,800,900,100i,200i,300i,400i,500i,600i,700i,800i,900i&display=swap"></noscript>
|
||||
<link rel="preload" href="./assets/css/space-grotesk.css?family=Space+Grotesk:300,400,500,600,700&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||
<noscript><link rel="stylesheet" href="./assets/css/space-grotesk.css?family=Space+Grotesk:300,400,500,600,700&display=swap"></noscript>
|
||||
<link href="./assets/quill/quill.snow.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<section data-bs-version="5.1" class="menu menu1 boldm5 cid-ttRnktJ11Q" once="menu" id="menu1-0">
|
||||
@ -45,7 +42,7 @@
|
||||
</a>
|
||||
</span>
|
||||
<span class="navbar-caption-wrap">
|
||||
<a class="navbar-caption display-4" href="index.html">Q-Mintership-Alpha (v0.84b)
|
||||
<a class="navbar-caption display-4" href="index.html">Q-Mintership (v1.0b)
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
@ -64,12 +61,12 @@
|
||||
<img src="assets/images/again-edited-qortal-minting-icon-156x156.png" alt="">
|
||||
</a>
|
||||
</span>
|
||||
<span class="navbar-caption-wrap"><a class="navbar-caption text-primary display-4" href="index.html">Q-Mintership Alpha v0.84b<br></a></span>
|
||||
<span class="navbar-caption-wrap"><a class="navbar-caption text-primary display-4" href="index.html">Q-Mintership v1.0b<br></a></span>
|
||||
</div>
|
||||
<ul class="navbar-nav nav-dropdown" data-app-modern-menu="true"><li class="nav-item"><a class="nav-link link text-primary display-7" href="MINTERSHIP-FORUM"></a></li></ul>
|
||||
|
||||
|
||||
<div class="mbr-section-btn-main" role="tablist"><a class="btn btn-danger display-4" href="MINTERSHIP-FORUM">FORUM<br></a> <a class="btn admin-btn btn-secondary display-4" href="TOOLS">ADMIN TOOLS</a><a class="btn admin-btn btn-secondary display-4" href="ADMINBOARD">ADMIN BOARD</a><a class="btn btn-danger display-4" href="MINTERS">MINTER BOARD</a></div>
|
||||
<div class="mbr-section-btn-main" role="tablist"><a class="btn btn-danger display-4" href="MINTERSHIP-FORUM">FORUM<br></a> <a class="btn admin-btn btn-secondary display-4" href="TOOLS">ADMIN TOOLS</a><a class="btn admin-btn btn-secondary display-4" href="ADMINBOARD">ADMIN BOARD</a><a class="btn btn-danger display-4" href="MINTERS">MINTER BOARD</a><a class="btn btn-danger display-4" href="ADDREMOVEADMIN">ARA BOARD</a></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@ -104,6 +101,31 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-6 col-md-6 item features-image active">
|
||||
<a class="item-link" href="ADDREMOVEADMIN">
|
||||
<div class="item-wrapper">
|
||||
<img src="assets/images/mbr-1818x1212.jpg" alt="Admin Board" data-slide-to="1" data-bs-slide-to="1">
|
||||
<div class="item-content">
|
||||
<h2 class="card-title mbr-fonts-style display-2">
|
||||
Promote / Demote Minter Admins</h2>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div><div class="col-12 col-lg-6 col-md-6 item features-image">
|
||||
<a class="item-link" href="MINTERSHIP-FORUM">
|
||||
<div class="item-wrapper">
|
||||
<img src="assets/images/mbr-1-1818x1212.jpg" alt="Mintership Forum" data-slide-to="0" data-bs-slide-to="0">
|
||||
<div class="item-content">
|
||||
<h2 class="card-title mbr-fonts-style display-2">Mintership Forum</h2>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
@ -169,6 +191,23 @@
|
||||
|
||||
<section data-bs-version="5.1" class="content7 boldm5 cid-uufIRKtXOO" id="content7-6">
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-7 card">
|
||||
<div class="title-wrapper">
|
||||
<h2 class="mbr-section-title mbr-fonts-style display-2">
|
||||
v1.0beta 01-21-2025</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-5 card">
|
||||
<div class="text-wrapper">
|
||||
<p class="mbr-text mbr-fonts-style display-7">
|
||||
<b><u>v1.0b information coming soon.</u></b>- <b>All Features related to new featureTriggers is completed and tested.</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-7 card">
|
||||
|
1434
project.mobirise
1434
project.mobirise
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user