main #5

Merged
Quick_Mythril merged 6 commits from crowetic/Q-Mintership-Alpha:main into main 2025-01-23 00:04:52 +00:00
4 changed files with 911 additions and 0 deletions
Showing only changes of commit 8ade0b60aa - Show all commits

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

911
assets/js/ARBoard.js Normal file
View File

@ -0,0 +1,911 @@
let minterGroupAddresses
let minterAdminAddresses
let isTest = false
let isAddRemoveBoard = true
const addRemoveIdentifierPrefix = "QM-AR-card"
const loadAddRemoveAdminPage = async () => {
console.log("Loading Add/Remove Admin page...")
const bodyChildren = document.body.children
for (let i = bodyChildren.length - 1; i >= 0; i--) {
const child = bodyChildren[i]
if (!child.classList.contains("menu")) {
child.remove()
}
}
const mainContainer = document.createElement("div")
mainContainer.className = "add-remove-admin-main"
mainContainer.style = "padding: 20px; text-align: center;"
mainContainer.innerHTML = `
<h1 style="color: lightblue;">Minter Admin Management</h1>
<p style="font-size:0.95rem; color: white;">
This page allows proposing the promotion of an existing minter to admin,
or demotion of an existing admin back to a normal minter.
</p>
<div id="admin-table-section" class="admin-table-section" style="margin-top: 2em;">
<h3 style="color:rgb(212, 212, 212);">Existing Minter Admins</h3>
<div id="admin-list-container" style="margin: 1em auto; max-width: 600px;"></div>
</div>
<div id="promotion-section" class="promotion-section" style="margin-top: 3em;">
<button id="propose-promotion-button" style="padding: 10px; color: white; background:rgb(7, 73, 71) ; cursor: pointer; border-radius: 5px;">
Propose a Minter for Admin Position
</button>
<div id="promotion-form-container" class="publish-card-view" style="display: none; margin-top: 1em;">
<form id="publish-card-form">
<h3>Create or Update Promotion/Demotion Proposal Card</h3>
<label for="minter-name-input">Input NAME (promotion):</label>
<input type="text" id="minter-name-input" maxlength="100" placeholder="input NAME of MINTER for PROMOTION" required>
<label for="card-header">Header:</label>
<input type="text" id="card-header" maxlength="100" placeholder="Header / Headline info" required>
<label for="card-content">Content:</label>
<textarea id="card-content" placeholder="Enter detailed information about why you are making this proposal for promotion/demotion. You may utilize links to additional data as well." required></textarea>
<label for="card-links">Links (qortal://...):</label>
<div id="links-container">
<input type="text" class="card-link" placeholder="Enter QDN link">
</div>
<button type="button" id="add-link-button">Add Another Link</button>
<button type="submit" id="submit-publish-button">Publish Card</button>
<button type="button" id="cancel-publish-button">Cancel</button>
</form>
</div>
</div>
<div id="existing-proposals-section" class="proposals-section" style="margin-top: 3em; display: flex; flex-direction: column; justify-content: center; align-items: center;">
<h3 style="color: #ddd;">Existing Promotion/Demotion Proposals</h3>
<button id="refresh-cards-button" class="refresh-cards-button" style="padding: 10px;">Refresh Proposal Cards</button>
</div>
<div id="cards-container" class="cards-container" style="margin-top: 1rem"">
<!-- We'll fill this with existing proposal cards -->
</div>
`
document.body.appendChild(mainContainer)
document.getElementById("propose-promotion-button").addEventListener("click", async () => {
try {
const fetchedCard = await fetchExistingARCard(addRemoveIdentifierPrefix)
if (fetchedCard) {
// An existing card is found
if (isTest) {
const updateCard = confirm("You have already published a promotion card, would you like to update that one or publish a new one?")
if (updateCard) {
isExistingCard = true
await loadCardIntoForm(existingCardData)
alert("Edit your existing card and publish.")
} else {
alert("New Card Selected: You can now create a new promotion card.")
isExistingCard = false
existingCardData = {}
document.getElementById("publish-card-form").reset()
}
} else {
// Not in test mode, force editing
alert("A card already exists. Publishing of multiple cards is not allowed. Please update your card.")
isExistingCard = true
await loadCardIntoForm(existingCardData)
}
} else {
// No existing card found
console.log("No existing card found. Creating a new card.")
isExistingCard = false
}
// Show the form
const publishCardView = document.getElementById("promotion-form-container")
publishCardView.style.display = 'flex'
// publishCardView.style.display === "none" ? "flex" : "none"
// document.getElementById("existing-proposals-section").style.display = "none"
const proposeButton = document.getElementById('propose-promotion-button')
proposeButton.style.display = 'none'
// proposeButton.style.display === 'flex' ? 'none' : 'flex'
} catch (error) {
console.error("Error checking for existing card:", error)
alert("Failed to check for existing card. Please try again.")
}
})
document.getElementById("refresh-cards-button").addEventListener("click", async () => {
const cardsContainer = document.getElementById("cards-container")
cardsContainer.innerHTML = "<p>Refreshing cards...</p>"
await loadCards(addRemoveIdentifierPrefix)
})
document.getElementById("cancel-publish-button").addEventListener("click", async () => {
// const cardsContainer = document.getElementById("existing-proposals-section")
// cardsContainer.style.display = "flex" // Restore visibility
const publishCardView = document.getElementById("promotion-form-container")
publishCardView.style.display = "none" // Hide the publish form
const proposeButton = document.getElementById('propose-promotion-button')
proposeButton.style.display = 'flex'
// proposeButton.style.display === 'flex' ? 'none' : 'flex'
})
document.getElementById("add-link-button").addEventListener("click", async () => {
const linksContainer = document.getElementById("links-container")
const newLinkInput = document.createElement("input")
newLinkInput.type = "text"
newLinkInput.className = "card-link"
newLinkInput.placeholder = "Enter QDN link"
linksContainer.appendChild(newLinkInput)
})
document.getElementById("publish-card-form").addEventListener("submit", async (event) => {
event.preventDefault()
await publishARCard(addRemoveIdentifierPrefix)
})
await featureTriggerCheck()
await loadCards(addRemoveIdentifierPrefix)
await displayExistingMinterAdmins()
await fetchAllARTxData()
}
const toggleProposeButton = () => {
const proposeButton = document.getElementById('propose-promotion-button')
proposeButton.style.display =
proposeButton.style.display === 'flex' ? 'none' : 'flex'
}
let addAdminTxs
let remAdminTxs
const fetchAllARTxData = async () => {
const addAdmTx = "ADD_GROUP_ADMIN"
const remAdmTx = "REMOVE_GROUP_ADMIN"
const filterAddTransactions = (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()
}
const filterRemoveTransactions = (rawTransactions) => {
// Group transactions by member
const adminTxMap = rawTransactions.reduce((map, tx) => {
if (!map[tx.admin]) {
map[tx.admin] = []
}
map[tx.admin].push(tx)
return map
}, {})
// Filter out members with both pending and non-pending transactions
return Object.values(adminTxMap)
.filter((txs) => !(txs.some(tx => tx.approvalStatus === 'PENDING') &&
txs.some(tx => tx.approvalStatus !== 'PENDING')))
.flat()
}
// Fetch ban transactions
const allAddTxs = await searchTransactions({
txTypes: [addAdmTx],
confirmationStatus: 'CONFIRMED',
limit: 0,
reverse: true,
offset: 0,
startBlock: 1990000,
blockLimit: 0,
txGroupId: 694,
})
// Filter out 'PENDING'
addAdminTxs = filterAddTransactions(allAddTxs)
console.warn('addAdminTxData (no PENDING nor past+PENDING):', addAdminTxs)
// Fetch kick transactions
const allRemTxs = await searchTransactions({
txTypes: [remAdmTx],
confirmationStatus: 'CONFIRMED',
limit: 0,
reverse: true,
offset: 0,
startBlock: 1990000,
blockLimit: 0,
txGroupId: 694,
})
// Filter out 'PENDING'
remAdminTxs = filterRemoveTransactions(allRemTxs)
console.warn('remAdminTxData (no PENDING nor past+PENDING):', remAdminTxs)
}
const displayExistingMinterAdmins = async () => {
const adminListContainer = document.getElementById("admin-list-container")
adminListContainer.innerHTML =
"<p style='color: #999; font-size: 1.1rem;'>Loading existing admins...</p>"
try {
// 1) Fetch addresses
const admins = await fetchMinterGroupAdmins()
minterAdminAddresses = admins.map(m => m.member)
let rowsHtml = "";
for (const adminAddr of admins) {
if (adminAddr.member === nullAddress) {
// Display a "NULL ACCOUNT" row
rowsHtml += `
<tr>
<td style="border: 1px solid #ccc; padding: 4px; color: #aaa;">
NULL ACCOUNT
</td>
<td style="border: 1px solid #ccc; padding: 4px; color: #aaa;">
${nullAddress}
</td>
<td style="border: 1px solid #ccc; padding: 4px; color: #aaa;">
<!-- No button, or a dash. -->
</td>
</tr>
`
continue
}
// Attempt to get name
let adminName
try {
adminName = await getNameFromAddress(adminAddr.member)
} catch (err) {
console.warn(`Error fetching name for ${adminAddr.member}:`, err)
adminName = null
}
const displayName = adminName && adminName !== adminAddr.member ? adminName : "(No Name)"
rowsHtml += `
<tr>
<td style="border: 1px solid rgb(150, 199, 224); font-size: 1.5rem; padding: 4px; color:rgb(70, 156, 196)">${displayName}</td>
<td style="border: 1px solid rgb(106, 203, 179); font-size: 1rem; padding: 4px; color:rgb(120, 150, 163);">${adminAddr.member}</td>
<td style="border: 1px solid rgb(231, 112, 112); padding: 4px;">
<button
style="padding: 5px; background: red; color: white; border-radius: 3px; cursor: pointer;"
onclick="handleProposeDemotionWrapper('${adminName}', '${adminAddr.member}')"
>
Propose Demotion
</button>
</td>
</tr>
`
}
// 3) Build the table
const tableHtml = `
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="background:rgb(21, 36, 18); color:rgb(183, 208, 173); font-size: 1.5rem;">
<th style="border: 1px solid rgb(34, 118, 129); padding: 4px;">Admin Name</th>
<th style="border: 1px solid rgb(90, 122, 122); padding: 4px;">Admin Address</th>
<th style="border: 1px solid rgb(138, 49, 49); padding: 4px;">Actions</th>
</tr>
</thead>
<tbody>
${rowsHtml}
</tbody>
</table>
`
adminListContainer.innerHTML = tableHtml
} catch (err) {
console.error("Error fetching minter admins:", err)
adminListContainer.innerHTML =
"<p style='color: red;'>Failed to load admins.</p>"
}
}
const handleProposeDemotionWrapper = (adminName, adminAddress) => {
// Call the async function and handle any unhandled rejections
handleProposeDemotion(adminName, adminAddress).catch(error => {
console.error(`Error in handleProposeDemotionWrapper:`, error)
alert("An unexpected error occurred. Please try again.")
})
}
const handleProposeDemotion = async (adminName, adminAddress) => {
console.log(`Proposing demotion for: ${adminName} (${adminAddress})`)
const proposeButton = document.getElementById('propose-promotion-button')
proposeButton.style.display = 'none'
const fetchedCard = await fetchExistingARCard(addRemoveIdentifierPrefix, adminName)
if (fetchedCard) {
alert("A card already exists. Publishing of multiple cards is not allowed. Please update your card.")
isExistingCard = true
await loadCardIntoForm(fetchedCard)
}
// Populate the form with the admin's name
const nameInput = document.getElementById("minter-name-input")
nameInput.value = adminName
// Display the form if it's hidden
const formContainer = document.getElementById("promotion-form-container")
formContainer.style.display = "flex"
// Optionally hide other sections (e.g., the existing proposals section)
// const proposalsSection = document.getElementById("existing-proposals-section")
// proposalsSection.style.display = "none"
// Notify the user to fill out the rest
alert(`Admin "${adminName}" has been selected for demotion. Please fill out the rest of the form.`)
}
const fetchExistingARCard = async (cardIdentifierPrefix, minterName) => {
try {
// 1. Fetch all cards with the specified prefix
const response = await searchSimple(
'BLOG_POST',
`${cardIdentifierPrefix}`,
'', // Empty name to fetch all cards
0,
0,
'',
true
)
console.log(`SEARCH_QDN_RESOURCES response: ${JSON.stringify(response, null, 2)}`)
if (!response || !Array.isArray(response) || response.length === 0) {
console.log("No cards found.")
return null
}
// 2. Fetch minterGroupAddresses if not already fetched
if (!minterGroupAddresses) {
const groupData = await fetchMinterGroupMembers()
minterGroupAddresses = groupData.map((m) => m.member)
}
// 3. Validate all fetched cards and check for duplicate `minterName`
const validatedCards = await Promise.all(
response.map(async (card) => {
const isValid = await validateCardStructure(card)
if (!isValid) return null
// Fetch full card data for validation
const cardDataResponse = await qortalRequest({
action: "FETCH_QDN_RESOURCE",
name: card.name,
service: "BLOG_POST",
identifier: card.identifier,
})
// Check if `minterName` matches the input or is a duplicate
if (cardDataResponse.minterName === minterName) {
console.log(`Card with the same minterName found: ${minterName}`)
return {
card,
cardData: cardDataResponse,
}
}
return null
})
)
// 4. Filter out null results and check for duplicates
const matchingCards = validatedCards.filter((result) => result !== null)
if (matchingCards.length > 0) {
const { card, cardData } = matchingCards[0] // Use the first matching card
// Determine if the card is a promotion card
// const nameInfo = await getNameInfo(cardData.minterName)
// const ownerAddress = nameInfo?.owner
// Set flags and return the existing card data
existingCardIdentifier = card.identifier
existingCardData = cardData
isExistingCard = true
return {
cardData
}
}
console.log("No valid cards found or no matching minterName.")
return null
} catch (error) {
console.error("Error fetching existing AR card:", error)
return null
}
}
const publishARCard = async (cardIdentifierPrefix) => {
const minterNameInput = document.getElementById("minter-name-input").value.trim()
const potentialNameInfo = await getNameInfo(minterNameInput)
let minterName
let address
let isPromotionCard
if (potentialNameInfo.owner) {
console.log(`MINTER NAME FOUND:`, minterNameInput)
minterName = minterNameInput
address = potentialNameInfo.owner
} else {
console.warn(`user input an address?...`, minterNameInput)
if (!address){
const validAddress = await getAddressInfo(minterNameInput)
if (validAddress){
address = minterNameInput
} else {
console.error(`input address by user INVALID`, minterNameInput)
alert(`You have input an invalid address! Please try again...`)
return
}
}
const checkForName = await getNameFromAddress(minterNameInput)
if (checkForName) {
minterName = checkForName
} else if (!checkForName && address){
console.warn(`user input an address that has no name...`)
alert(`you have input an address that has no name, the address will need to register a name prior to being able to be promoted`)
return
} else {
console.warn(`Input was either an invalid name, or incorrect address?`, minterNameInput)
alert(`Your input could not be validated, check the name/address and try again!`)
return
}
}
const minterGroupData = await fetchMinterGroupMembers()
minterGroupAddresses = minterGroupData.map(m => m.member)
const minterAdminGroupData = await fetchMinterGroupAdmins()
minterAdminAddresses = minterAdminGroupData.map(m => m.member)
if (minterGroupAddresses.includes(address)) {
isPromotionCard = true
console.warn(`address is a MINTER, this is a promotion card...`)
}
if (minterAdminAddresses.includes(address)){
isPromotionCard = false
console.warn(`this is a DEMOTION`, address)
}
if (!minterAdminAddresses.includes(address) && !minterGroupAddresses.includes(address)) {
console.error(`you cannot publish a card here unless the user is a MINTER or an ADMIN!`)
alert(`Card cannot be published for an account that is neither a minter nor an admin! This board is for Promotions and Demotions of Admins ONLY!`)
return
}
const header = document.getElementById("card-header").value.trim()
const content = document.getElementById("card-content").value.trim()
const links = Array.from(document.querySelectorAll(".card-link"))
.map(input => input.value.trim())
.filter(link => link.startsWith("qortal://"))
if (!header || !content) {
alert("Header and content are required!")
return
}
const cardIdentifier = isExistingCard ? existingCardIdentifier : `${cardIdentifierPrefix}-${await uid()}`
const pollName = `${cardIdentifier}-poll`
const pollDescription = `AR Board Card Proposed By: ${userState.accountName}`
const cardData = {
minterName,
header,
content,
links,
creator: userState.accountName,
timestamp: Date.now(),
poll: pollName,
promotionCard: isPromotionCard
}
try {
let base64CardData = await objectToBase64(cardData)
if (!base64CardData) {
console.log(`initial base64 object creation with objectToBase64 failed, using btoa...`)
base64CardData = btoa(JSON.stringify(cardData))
}
await qortalRequest({
action: "PUBLISH_QDN_RESOURCE",
name: userState.accountName,
service: "BLOG_POST",
identifier: cardIdentifier,
data64: base64CardData,
})
if (!isExistingCard){
await qortalRequest({
action: "CREATE_POLL",
pollName,
pollDescription,
pollOptions: ['Yes, No'],
pollOwnerAddress: userState.accountAddress,
})
alert("Card and poll published successfully!")
}
if (isExistingCard){
alert("Card Updated Successfully! (No poll updates are possible at this time...)")
isExistingCard = false
}
document.getElementById("publish-card-form").reset()
document.getElementById("promotion-form-container").style.display = "none"
// document.getElementById("cards-container").style.display = "flex"
await loadCards(addRemoveIdentifierPrefix)
} catch (error) {
console.error("Error publishing card or poll:", error)
alert("Failed to publish card and poll.")
}
}
const checkAndDisplayActions = async (adminYes, name, cardIdentifier) => {
const latestBlockInfo = await getLatestBlockInfo()
const isBlockPassed = latestBlockInfo.height >= GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT
let minAdminCount
const minterAdmins = await fetchMinterGroupAdmins()
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}`)
minAdminCount = 9
} 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}`)
}
const addressInfo = await getNameInfo(name)
const address = addressInfo.owner
if (isBlockPassed) {
console.warn(`feature trigger has passed, checking for approval requirements`)
const addAdminApprovalHtml = await checkGroupApprovalAndCreateButton(address, cardIdentifier, "ADD_GROUP_ADMIN")
const removeAdminApprovalHtml = await checkGroupApprovalAndCreateButton(address, cardIdentifier, "REMOVE_GROUP_ADMIN")
if (addAdminApprovalHtml) {
return addAdminApprovalHtml
}
if (removeAdminApprovalHtml) {
return removeAdminApprovalHtml
}
}
if (!minterGroupAddresses) {
const minterGroupData = await fetchMinterGroupMembers()
minterGroupAddresses = minterGroupData.map(m => m.member)
}
if (!minterAdminAddresses) {
const adminAddressData = await fetchMinterGroupAdmins()
minterAdminAddresses = adminAddressData.map(m => m.member)
}
if (!minterGroupAddresses.includes(userState.accountAddress)){
console.warn(`User is not in the MINTER group, no need for buttons`)
return null
}
if (adminYes >= minAdminCount && (minterAdminAddresses.includes(address))){
const removeAdminHtml = createRemoveAdminButton(name, cardIdentifier, address)
return removeAdminHtml
} else if (adminYes >= minAdminCount && (minterGroupAddresses.includes(address))){
const addAdminHtml = createAddAdminButton(name, cardIdentifier, address)
return addAdminHtml
}
}
const createAddAdminButton = (name, cardIdentifier, address) => {
return `
<div id="add-button-container-${cardIdentifier}" style="margin-top: 1em;">
<button onclick="handleAddMinterGroupAdmin('${name}','${address}')"
style="padding: 10px; background: rgb(4, 119, 134); color: white; border: none; cursor: pointer; border-radius: 5px;"
onmouseover="this.style.backgroundColor='rgb(11, 47, 24) '"
onmouseout="this.style.backgroundColor='rgb(4, 123, 134) '">
Create ADD_GROUP_ADMIN Tx
</button>
</div>
`
}
const createRemoveAdminButton = (name, cardIdentifier, address) => {
return `
<div id="add-button-container-${cardIdentifier}" style="margin-top: 1em;">
<button onclick="handleRemoveMinterGroupAdmin('${name}','${address}')"
style="padding: 10px; background: rgb(134, 4, 4); color: white; border: none; cursor: pointer; border-radius: 5px;"
onmouseover="this.style.backgroundColor='rgb(0, 0, 0) '"
onmouseout="this.style.backgroundColor='rgb(134, 4, 4) '">
Create REMOVE_GROUP_ADMIN Tx
</button>
</div>
`
}
const handleAddMinterGroupAdmin = async (name, address) => {
try {
// Optional block check
let txGroupId = 0
let member = address
// const { height: currentHeight } = await getLatestBlockInfo()
const isBlockPassed = await featureTriggerCheck()
if (isBlockPassed) {
console.log(`block height above featureTrigger Height, using group approval method...txGroupId 694`)
txGroupId = 694
}
const ownerPublicKey = await getPublicKeyFromAddress(userState.accountAddress)
const fee = 0.01
const rawTx = await createAddGroupAdminTransaction(ownerPublicKey, 694, member, txGroupId, fee)
const signedTx = await qortalRequest({
action: "SIGN_TRANSACTION",
unsignedBytes: rawTx
})
if (!signedTx) {
console.warn(`this only happens if the SIGN_TRANSACTION qortalRequest failed... are you using the legacy UI prior to this qortalRequest being added?`)
alert(`this only happens if the SIGN_TRANSACTION qortalRequest failed... are you using the legacy UI prior to this qortalRequest being added? Please talk to developers.`)
return
}
let txToProcess = signedTx
const processTx = await processTransaction(txToProcess)
if (typeof processTx === 'object') {
console.log("transaction success object:", processTx)
alert(`${name} kick successfully issued! Wait for confirmation...Transaction Response: ${JSON.stringify(processTx)}`)
} else {
console.log("transaction raw text response:", processTx)
alert(`TxResponse: ${JSON.stringify(processTx)}`)
}
} catch (error) {
console.error("Error removing minter:", error)
alert(`Error:${error}. Please try again.`)
}
}
const handleRemoveMinterGroupAdmin = async (name, address) => {
try {
// Optional block check
let txGroupId = 0
const admin = address
// const { height: currentHeight } = await getLatestBlockInfo()
const isBlockPassed = await featureTriggerCheck()
if (isBlockPassed) {
console.log(`block height above featureTrigger Height, using group approval method...txGroupId 694`)
txGroupId = 694
}
const ownerPublicKey = await getPublicKeyFromAddress(userState.accountAddress)
const fee = 0.01
const rawTx = await createRemoveGroupAdminTransaction(ownerPublicKey, 694, admin, txGroupId, fee)
const signedTx = await qortalRequest({
action: "SIGN_TRANSACTION",
unsignedBytes: rawTx
})
if (!signedTx) {
console.warn(`this only happens if the SIGN_TRANSACTION qortalRequest failed... are you using the legacy UI prior to this qortalRequest being added?`)
alert(`this only happens if the SIGN_TRANSACTION qortalRequest failed... are you using the legacy UI prior to this qortalRequest being added? Please talk to developers.`)
return
}
let txToProcess = signedTx
const processTx = await processTransaction(txToProcess)
if (typeof processTx === 'object') {
console.log("transaction success object:", processTx)
alert(`${name} kick successfully issued! Wait for confirmation...Transaction Response: ${JSON.stringify(processTx)}`)
} else {
console.log("transaction raw text response:", processTx)
alert(`TxResponse: ${JSON.stringify(processTx)}`)
}
} catch (error) {
console.error("Error removing minter:", error)
alert(`Error:${error}. Please try again.`)
}
}
const fallbackMinterCheck = async (minterName, minterGroupMembers, minterAdmins) => {
// Ensure we have addresses
if (!minterGroupMembers) {
console.warn("No minterGroupMembers array was passed in fallback check!")
return false
}
const minterGroupAddresses = minterGroupMembers.map(m => m.member)
const adminAddresses = minterAdmins.map(m => m.member)
const minterAcctInfo = await getNameInfo(minterName)
if (!minterAcctInfo || !minterAcctInfo.owner) {
console.warn(`Name info not found or missing 'owner' for ${minterName}`)
return false
}
// If user is already in the group => we call it a "promotion card"
if (adminAddresses.includes(minterAcctInfo.owner)) {
console.warn(`display check found minterAdminCard - NOT a promotion card...`)
return false
} else {
return minterGroupAddresses.includes(minterAcctInfo.owner)
}
}
const createARCardHTML = async (cardData, pollResults, cardIdentifier, commentCount) => {
const { minterName, header, content, links, creator, timestamp, poll, promotionCard } = cardData
const formattedDate = new Date(timestamp).toLocaleString()
const minterAvatar = await getMinterAvatar(minterName)
const creatorAvatar = await getMinterAvatar(creator)
const linksHTML = links.map((link, index) => `
<button onclick="openLinkDisplayModal('${link}')">
${`Link ${index + 1} - ${link}`}
</button>
`).join("")
const minterGroupMembers = await fetchMinterGroupMembers()
const minterAdmins = await fetchMinterGroupAdmins()
let showPromotionCard = false
showPromotionCard = await fallbackMinterCheck(minterName, minterGroupMembers, minterAdmins)
// if (typeof promotionCard === 'boolean') {
// showPromotionCard = promotionCard;
// } else if (typeof promotionCard === 'string') {
// // Could be "true" or "false" or something else
// const lower = promotionCard.trim().toLowerCase()
// if (lower === "true") {
// showPromotionCard = true
// } else if (lower === "false") {
// showPromotionCard = false
// } else {
// // Unexpected string => fallback
// console.warn(`Unexpected string in promotionCard="${promotionCard}"`)
// showPromotionCard = await fallbackMinterCheck(minterName, minterGroupMembers)
// }
// } else if (promotionCard == null) {
// // null or undefined => fallback check
// console.warn(`No promotionCard field in card data, doing manual check...`)
// showPromotionCard = await fallbackMinterCheck(minterName, minterGroupMembers)
// } else {
// // If its an object or something else weird => fallback
// console.warn(`promotionCard has unexpected type, fallback...`)
// showPromotionCard = await fallbackMinterCheck(minterName, minterGroupMembers)
// }
let cardColorCode = (showPromotionCard) ? 'rgb(17, 44, 46)' : 'rgb(57, 11, 13)'
const promotionDemotionHtml = (showPromotionCard) ? `
<div class="support-header"><h5> REGARDING (Promotion): </h5></div>
<h3>${minterName}</h3>` :
`
<div class="support-header"><h5> REGARDING (Demotion): </h5></div>
${minterAvatar}
<h3>${minterName}</h3>`
if (!promotionDemotionHtml){
console.warn(`promotionDemotionHtml missing!`)
}
const { adminYes = 0, adminNo = 0, minterYes = 0, minterNo = 0, totalYes = 0, totalNo = 0, totalYesWeight = 0, totalNoWeight = 0, detailsHtml } = await processPollData(pollResults, minterGroupMembers, minterAdmins, creator, cardIdentifier)
createModal('links')
createModal('poll-details')
let actionsHtml
let altText
const verifiedName = await validateMinterName(minterName)
if (verifiedName) {
const accountInfo = await getNameInfo(verifiedName)
const accountAddress = accountInfo.owner
console.log(`name is validated, utilizing for removal features...${verifiedName}`)
const actionsHtmlCheck = await checkAndDisplayActions(adminYes, verifiedName, cardIdentifier)
actionsHtml = actionsHtmlCheck
if (!addAdminTxs || !remAdminTxs) {
await fetchAllARTxData()
}
if (addAdminTxs.some((addTx) => addTx.groupId === 694 && addTx.member === accountAddress)){
console.warn(`account was already adminified(PROMOTED), displaying as such...`)
cardColorCode = 'rgb(3, 11, 24)'
altText = `<h4 style="color:rgb(2, 94, 106); margin-bottom: 0.5em;">PROMOTED to ADMIN</h4>`
actionsHtml = ''
// =============================================================== if 'showPromotedDemoted' is wanted to be added.
// if (!showPromotedDemoted){
// console.warn(`promoted/demoted show checkbox is unchecked, and card is promoted, not displaying...`)
// return ''
// }
}
if (remAdminTxs.some((remTx) => remTx.groupId === 694 && remTx.admin === accountAddress)){
console.warn(`account was already UNadminified(DEMOTED), displaying as such...`)
cardColorCode = 'rgb(29, 4, 6)'
altText = `<h4 style="color:rgb(73, 24, 24); margin-bottom: 0.5em;">DEMOTED from ADMIN</h4>`
actionsHtml = ''
// if (!showPromotedDemoted) {
// console.warn(`promoted/demoted show checkbox is unchecked, card is demoted, not displaying...`)
// return ''
// }
}
} else {
console.log(`name could not be validated, assuming topic card (or some other issue with name validation) for removalActions`)
actionsHtml = ''
}
return `
<div class="admin-card" style="background-color: ${cardColorCode}">
<div class="minter-card-header">
<h2 class="support-header"> Created By: </h2>
${creatorAvatar}
<h2>${creator}</h2>
${promotionDemotionHtml}
<p>${header}</p>
${altText}
</div>
<div class="info">
${content}
</div>
<div class="support-header"><h5>LINKS</h5></div>
<div class="info-links">
${linksHTML}
</div>
<div class="results-header support-header"><h5>CURRENT 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>
${actionsHtml}
<div class="admin-results">
<span class="admin-yes">Admin Support: ${adminYes}</span>
<span class="admin-no">Admin Against: ${adminNo}</span>
</div>
<div class="minter-results">
<span class="minter-yes">Minter Yes: ${minterYes}</span>
<span class="minter-no">Minter No: ${minterNo}</span>
</div>
<div class="total-results">
<span class="total-yes">Total Yes: ${totalYes}</span>
<span class="total-yes">Weight: ${totalYesWeight}</span>
<span class="total-no">Total No: ${totalNo}</span>
<span class="total-no">Weight: ${totalNoWeight}</span>
</div>
</div>
<div class="support-header"><h5>ACTIONS FOR</h5><h5 style="color: #ffae42;">${minterName}</h5>
<p style="color: #c7c7c7; font-size: .65rem; margin-top: 1vh">(click COMMENTS button to open/close card comments)</p>
</div>
<div class="actions">
<div class="actions-buttons">
<button class="yes" onclick="voteYesOnPoll('${poll}')">YES</button>
<button id="comment-button-${cardIdentifier}" data-comment-count="${commentCount}" class="comment" onclick="toggleComments('${cardIdentifier}')">COMMENTS (${commentCount})</button>
<button class="no" onclick="voteNoOnPoll('${poll}')">NO</button>
</div>
</div>
<div id="comments-section-${cardIdentifier}" class="comments-section" style="display: none; margin-top: 20px;">
<div id="comments-container-${cardIdentifier}" class="comments-container"></div>
<textarea id="new-comment-${cardIdentifier}" placeholder="Input your comment..." style="width: 100%; margin-top: 10px;"></textarea>
<button onclick="postComment('${cardIdentifier}')">Post Comment</button>
</div>
<p style="font-size: 0.75rem; margin-top: 1vh; color: #4496a1">By: ${creator} - ${formattedDate}</p>
</div>
`
}