Compare commits

...

6 Commits

7 changed files with 262 additions and 115 deletions

View File

@ -708,31 +708,6 @@ body {
} }
/* Responsive design */
@media (max-width: 768px) {
.publish-card-view {
width: 90%;
padding: 2vh;
}
.publish-card-button {
font-size: 1.8vh;
padding: 1.5vh;
}
.publish-card-form button {
font-size: 1.8vh;
padding: 1.2vh;
}
}
.refresh-cards-button {
border-color: white;
border-radius: 1.5vh;
background-color: black;
color: white;
}
/* Responsive Design */ /* Responsive Design */
@media (max-width: 768px) { @media (max-width: 768px) {
.publish-card-view { .publish-card-view {
@ -754,8 +729,14 @@ body {
.refresh-cards-button { .refresh-cards-button {
border-color: white; border-color: white;
border-radius: 1.5vh; border-radius: 1.5vh;
background-color: black; background-color: rgba(0, 0, 0, 0.089);
color: white; color: white;
font-size: 0.9rem;
}
.refresh-cards-button:hover {
background-color: rgba(35, 129, 136, 0.137);
color: rgba(90, 201, 221, 0.793);
} }
/* Two cards per row on medium screens */ /* Two cards per row on medium screens */

View File

@ -60,11 +60,13 @@ const loadAddRemoveAdminPage = async () => {
<h3 style="color: #ddd;">Existing Promotion/Demotion Proposals</h3> <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> <button id="refresh-cards-button" class="refresh-cards-button" style="padding: 10px;">Refresh Proposal Cards</button>
<select id="time-range-select" style="margin-left: 10px; padding: 5px; font-size: 1.25rem; color: white; background-color: black;"> <select id="time-range-select" style="margin-left: 10px; padding: 5px; font-size: 1.25rem; color: white; background-color: black;">
<option value="0">Show All</option> <option value="0">All Creation Dates</option>
<option value="1">Last 1 day</option> <option value="1">Last 1 Day</option>
<option value="7">Last 7 days</option> <option value="7">Last 7 Days</option>
<option value="30" selected>Last 30 days</option> <option value="30">...Within 30 Days</option>
<option value="90">Last 90 days</option> <option value="45" selected>Published Within Last 45 Days</option>
<option value="60">...Within 60 Days</option>
<option value="90">...Within 90 Days</option>
</select> </select>
</div> </div>
<div id="cards-container" class="cards-container" style="margin-top: 1rem""> <div id="cards-container" class="cards-container" style="margin-top: 1rem"">
@ -117,6 +119,13 @@ const loadAddRemoveAdminPage = async () => {
linksContainer.appendChild(newLinkInput) linksContainer.appendChild(newLinkInput)
}) })
const timeRangeSelectCheckbox = document.getElementById('time-range-select')
if (timeRangeSelectCheckbox) {
timeRangeSelectCheckbox.addEventListener('change', async (event) => {
await loadCards(addRemoveIdentifierPrefix)
})
}
document.getElementById("publish-card-form").addEventListener("submit", async (event) => { document.getElementById("publish-card-form").addEventListener("submit", async (event) => {
event.preventDefault() event.preventDefault()
await publishARCard(addRemoveIdentifierPrefix) await publishARCard(addRemoveIdentifierPrefix)

View File

@ -84,12 +84,14 @@ const loadAdminBoardPage = async () => {
<option value="most-votes">Most Votes</option> <option value="most-votes">Most Votes</option>
</select> </select>
<select id="time-range-select" style="margin-left: 10px; padding: 5px; font-size: 1.25rem; color: white; background-color: black;"> <select id="time-range-select" style="margin-left: 10px; padding: 5px; font-size: 1.25rem; color: white; background-color: black;">
<option value="0">Show All</option> <option value="0">All Creation Dates</option>
<option value="1">Last 1 day</option> <option value="1">Last 1 Day</option>
<option value="7">Last 7 days</option> <option value="7">Last 7 Days</option>
<option value="30" selected>Last 30 days</option> <option value="30">...Within 30 Days</option>
<option value="90">Last 90 days</option> <option value="45" selected>Published Within Last 45 Days</option>
</select> <option value="60">...Within 60 Days</option>
<option value="90">...Within 90 Days</option>
</select>
<div class="show-card-checkbox" style="margin-top: 1em;"> <div class="show-card-checkbox" style="margin-top: 1em;">
<input type="checkbox" id="admin-show-hidden-checkbox" name="adminHidden" /> <input type="checkbox" id="admin-show-hidden-checkbox" name="adminHidden" />
<label for="admin-show-hidden-checkbox">Show User-Hidden Cards?</label> <label for="admin-show-hidden-checkbox">Show User-Hidden Cards?</label>
@ -1078,7 +1080,7 @@ const createRemoveButtonHtml = (name, cardIdentifier) => {
const handleKickMinter = async (minterName) => { const handleKickMinter = async (minterName) => {
try { try {
isAddress = await getAddressInfo(minterName) let isAddress = await getAddressInfo(minterName)
// Optional block check // Optional block check
let txGroupId = 0 let txGroupId = 0
@ -1091,7 +1093,7 @@ const handleKickMinter = async (minterName) => {
// Get the minter address from name info // Get the minter address from name info
let minterAddress let minterAddress
if (!isAddress){ if (!isAddress.address || !isAddress.address != minterName){
const minterNameInfo = await getNameInfo(minterName) const minterNameInfo = await getNameInfo(minterName)
minterAddress = minterNameInfo?.owner minterAddress = minterNameInfo?.owner
} else { } else {
@ -1107,7 +1109,7 @@ const handleKickMinter = async (minterName) => {
const reason = 'Kicked by Minter Admins' const reason = 'Kicked by Minter Admins'
const fee = 0.01 const fee = 0.01
const rawKickTransaction = await createGroupKickTransaction(minterAddress, adminPublicKey, 694, minterAddress, reason, txGroupId, fee) const rawKickTransaction = await createGroupKickTransaction(adminPublicKey, 694, minterAddress, reason, txGroupId, fee)
const signedKickTransaction = await qortalRequest({ const signedKickTransaction = await qortalRequest({
action: "SIGN_TRANSACTION", action: "SIGN_TRANSACTION",
@ -1138,7 +1140,7 @@ const handleKickMinter = async (minterName) => {
} }
const handleBanMinter = async (minterName) => { const handleBanMinter = async (minterName) => {
isAddress = await getAddressInfo(minterName) let isAddress = await getAddressInfo(minterName)
try { try {
let txGroupId = 0 let txGroupId = 0
// const { height: currentHeight } = await getLatestBlockInfo() // const { height: currentHeight } = await getLatestBlockInfo()
@ -1151,9 +1153,9 @@ const handleBanMinter = async (minterName) => {
txGroupId = 694 txGroupId = 694
} }
let minterAddress let minterAddress
if (!isAddress) { if (!isAddress.address || !isAddress.address != minterName){
const minterNameInfo = await getNameInfo(minterName) const minterNameInfo = await getNameInfo(minterName)
const minterAddress = minterNameInfo?.owner minterAddress = minterNameInfo?.owner
} else { } else {
minterAddress = minterName minterAddress = minterName
} }

View File

@ -25,42 +25,99 @@ const loadMinterBoardPage = async () => {
const publishButtonColor = '#527c9d' const publishButtonColor = '#527c9d'
const minterBoardNameColor = '#527c9d' const minterBoardNameColor = '#527c9d'
mainContent.innerHTML = ` mainContent.innerHTML = `
<div class="minter-board-main" style="padding: 20px; text-align: center;"> <div class="minter-board-main" style="padding: 0.5vh; text-align: center;">
<h1 style="color: ${minterBoardNameColor};">Minter Board</h1>
<p style="font-size: 1.25em;"> Publish a Minter Card with Information, and obtain and view the support of the community. Welcome to the Minter Board!</p> <!-- Board Title + Intro -->
<button id="publish-card-button" class="publish-card-button" style="margin: 20px; padding: 10px; background-color: ${publishButtonColor}">Publish Minter Card</button> <h1 style="color: #527c9d;">The Minter Board</h1>
<button id="refresh-cards-button" class="refresh-cards-button" style="padding: 10px;">Refresh Cards</button> <p style="font-size: 1.2em; color:rgb(85, 119, 101)">
<select id="sort-select" style="margin-left: 10px; padding: 5px; font-size: 1.25rem; color:rgb(38, 106, 106); background-color: black;"> The Minter Board is where Minting Rights are Delegated.
<option value="newest" selected>Sort by Date</option> </p>
<option value="name">Sort by Name</option> <p style="font-size: 1.1em; color:rgb(85, 119, 119)">
<option value="recent-comments">Newest Comments</option> To obtain minting rights, click 'PUBLISH CARD' and create your card. A subsequent vote will approve/deny your card.
<option value="least-votes">Least Votes</option> </p>
<option value="most-votes">Most Votes</option> <p>
</select> After your card has received the necessary invite, return to the card and click the Join Group button to join the MINTER group.
<select id="time-range-select" style="margin-left: 10px; padding: 5px; font-size: 1.25rem; color: white; background-color: black;"> (A Detailed how-to guide will be coming soon.)
<option value="0">Show All</option> </p>
<option value="1">Last 1 day</option>
<option value="7">Last 7 days</option> <div class="card-display-options">
<option value="30" selected>Last 30 days</option> <!-- Centered heading -->
<option value="90">Last 90 days</option> <h4 class="options-heading"style="color: #527c9d;">CARD DISPLAY OPTIONS</h4>
</select>
<div id="cards-container" class="cards-container" style="margin-top: 20px;"></div> <!-- A flex container for all the controls (sort, time range, checkbox) -->
<div id="publish-card-view" class="publish-card-view" style="display: none; text-align: left; padding: 20px;"> <div class="options-row">
<form id="publish-card-form" class="publish-card-form"> <!-- Sort by -->
<h3>Create or Update Your Card</h3> <label for="sort-select" class="options-label">Sort By:</label>
<label for="card-header">Header:</label> <select id="sort-select" class="options-select">
<input type="text" id="card-header" maxlength="100" placeholder="Enter card header" required> <option value="newest" selected>Date</option>
<label for="card-content">Content:</label> <option value="name">Name</option>
<textarea id="card-content" placeholder="Enter detailed information about why you would like to be a minter... the more the better, and links to things you have published on QDN will help a lot! Give the Minter Admins things to make decisions by!" required></textarea> <option value="recent-comments">Newest Comments</option>
<label for="card-links">Links (qortal://...):</label> <option value="least-votes">Least Votes</option>
<div id="links-container"> <option value="most-votes">Most Votes</option>
<input type="text" class="card-link" placeholder="Enter QDN link"> </select>
<!-- Time range -->
<label for="time-range-select" class="options-label">Show Cards:</label>
<select id="time-range-select" class="options-select">
<option value="0">Show ALL Cards Published</option>
<option value="1">...Within Last 1 Day</option>
<option value="7">...Within Last 7 Days</option>
<option value="30">...Within 30 Days</option>
<option value="45" selected>Published Within Last 45 Days</option>
<option value="60">...Within 60 Days</option>
<option value="90">...Within 90 Days</option>
</select>
<!-- Show existing checkbox -->
<label class="options-check">
<input type="checkbox" id="show-existing-checkbox" />
Show Existing Minter Cards (History)
</label>
</div>
</div>
<!-- Card counter heading centered, with actual counter below if desired -->
<div style="margin-bottom: 1em;">
<div style="text-align: center; margin-top: 0.5em;">
<span id="board-card-counter" style="font-size: 1rem; color:rgb(153, 203, 204); padding: 0.5em;">
<!-- e.g. "5 cards found" -->
</span>
</div> </div>
<button type="button" id="add-link-button">Add Another Link</button> </div>
<button type="submit" id="submit-publish-button">Publish Card</button>
<button type="button" id="cancel-publish-button">Cancel</button> <!-- Row for Publish / Refresh actions -->
</form> <div class="card-actions" style="margin-bottom: 1em;">
</div> <button id="publish-card-button" class="publish-card-button">
PUBLISH CARD
</button>
<button id="refresh-cards-button" class="refresh-cards-button"
style="padding: 1vh;">
REFRESH CARDS
</button>
</div>
<!-- Container for displayed cards -->
<div id="cards-container" class="cards-container" style="margin-top: 2vh;"></div>
<!-- Hidden Publish Card Form -->
<div id="publish-card-view" class="publish-card-view" style="display: none; text-align: left; padding: 2vh;">
<form id="publish-card-form" class="publish-card-form">
<h3>Create or Update Your Card</h3>
<label for="card-header">Header:</label>
<input type="text" id="card-header" maxlength="100" placeholder="Enter card header" required>
<label for="card-content">Content:</label>
<textarea id="card-content" placeholder="Enter detailed information about why you would like to be a minter... the more the better..." 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>
` `
document.body.appendChild(mainContent) document.body.appendChild(mainContent)
@ -144,6 +201,13 @@ const loadMinterBoardPage = async () => {
await loadCards(minterCardIdentifierPrefix) await loadCards(minterCardIdentifierPrefix)
}) })
const showExistingCardsCheckbox = document.getElementById('show-existing-checkbox')
if (showExistingCardsCheckbox) {
showExistingCardsCheckbox.addEventListener('change', async (event) => {
await loadCards(minterCardIdentifierPrefix)
})
}
await featureTriggerCheck() await featureTriggerCheck()
await loadCards(minterCardIdentifierPrefix) await loadCards(minterCardIdentifierPrefix)
} }
@ -367,6 +431,12 @@ const loadCards = async (cardIdentifierPrefix) => {
const cardsContainer = document.getElementById("cards-container") const cardsContainer = document.getElementById("cards-container")
let isARBoard = false let isARBoard = false
cardsContainer.innerHTML = "<p>Loading cards...</p>" cardsContainer.innerHTML = "<p>Loading cards...</p>"
const counterSpan = document.getElementById("board-card-counter")
if (counterSpan) {
// Clear or show "Loading..."
counterSpan.textContent = "(loading...)"
}
if (cardIdentifierPrefix.startsWith("QM-AR-card")) { if (cardIdentifierPrefix.startsWith("QM-AR-card")) {
isARBoard = true isARBoard = true
@ -375,6 +445,9 @@ const loadCards = async (cardIdentifierPrefix) => {
let afterTime = 0 let afterTime = 0
const timeRangeSelect = document.getElementById("time-range-select") const timeRangeSelect = document.getElementById("time-range-select")
const showExistingCheckbox = document.getElementById("show-existing-checkbox")
const showExisting = showExistingCheckbox && showExistingCheckbox.checked
if (timeRangeSelect) { if (timeRangeSelect) {
const days = parseInt(timeRangeSelect.value, 10) const days = parseInt(timeRangeSelect.value, 10)
if (days > 0) { if (days > 0) {
@ -440,6 +513,7 @@ const loadCards = async (cardIdentifierPrefix) => {
// else 'newest' => do nothing (already sorted newest-first by your process functions). // else 'newest' => do nothing (already sorted newest-first by your process functions).
// Create the 'finalCardsArray' that includes the data, etc. // Create the 'finalCardsArray' that includes the data, etc.
let finalCardsArray = [] let finalCardsArray = []
let alreadyMinterCards = []
cardsContainer.innerHTML = '' cardsContainer.innerHTML = ''
for (const card of finalCards) { for (const card of finalCards) {
try { try {
@ -477,8 +551,14 @@ const loadCards = async (cardIdentifierPrefix) => {
} else { } else {
const isAlreadyMinter = await verifyMinter(cardDataResponse.creator) const isAlreadyMinter = await verifyMinter(cardDataResponse.creator)
if (isAlreadyMinter) { if (isAlreadyMinter) {
console.warn(`card IS ALREADY a minter, NOT displaying following identifier on the MinterBoard: ${card.identifier}`) console.warn(`card IS ALREADY a minter, adding to alreadyMinterCards array: ${card.identifier}`)
removeSkeleton(card.identifier) removeSkeleton(card.identifier)
alreadyMinterCards.push({
...card,
cardDataResponse,
pollPublisherAddress,
cardPublisherAddress
})
continue continue
} }
} }
@ -489,6 +569,12 @@ const loadCards = async (cardIdentifierPrefix) => {
pollPublisherAddress, pollPublisherAddress,
cardPublisherAddress, cardPublisherAddress,
}) })
if (counterSpan) {
const displayedCount = finalCardsArray.length
const alreadyMinterCount = alreadyMinterCards.length
// If you want to show both
counterSpan.textContent = `(${displayedCount} cards, ${alreadyMinterCount} existingMinters)`
}
} catch (err) { } catch (err) {
console.error(`Error preparing card ${card.identifier}`, err) console.error(`Error preparing card ${card.identifier}`, err)
removeSkeleton(card.identifier) removeSkeleton(card.identifier)
@ -531,9 +617,39 @@ const loadCards = async (cardIdentifierPrefix) => {
replaceSkeleton(cardObj.identifier, finalCardHTML) replaceSkeleton(cardObj.identifier, finalCardHTML)
} }
if (showExisting && alreadyMinterCards.length > 0) {
console.warn(`Rendering Existing Minter cards because user selected showExisting`)
for (const mintedCardObj of alreadyMinterCards) {
const skeletonHTML = createSkeletonCardHTML(mintedCardObj.identifier)
cardsContainer.insertAdjacentHTML("beforeend", skeletonHTML)
const pollResults = await fetchPollResults(mintedCardObj.cardDataResponse.poll)
const commentCount = await countComments(mintedCardObj.identifier)
const cardUpdatedTime = mintedCardObj.updated || null
const bgColor = generateDarkPastelBackgroundBy(mintedCardObj.name)
const isExistingMinter = true
const finalCardHTML = await createCardHTML(
mintedCardObj.cardDataResponse,
pollResults,
mintedCardObj.identifier,
commentCount,
cardUpdatedTime,
bgColor,
mintedCardObj.cardPublisherAddress,
isExistingMinter
)
replaceSkeleton(mintedCardObj.identifier, finalCardHTML)
}
}
} catch (error) { } catch (error) {
console.error("Error loading cards:", error) console.error("Error loading cards:", error)
cardsContainer.innerHTML = "<p>Failed to load cards.</p>" cardsContainer.innerHTML = "<p>Failed to load cards.</p>"
if (counterSpan) {
counterSpan.textContent = "(error loading)"
}
} }
} }
@ -750,7 +866,6 @@ const loadCardIntoForm = async (cardData) => {
// Main function to publish a new Minter Card ----------------------------------------------- // Main function to publish a new Minter Card -----------------------------------------------
const publishCard = async (cardIdentifierPrefix) => { const publishCard = async (cardIdentifierPrefix) => {
const minterGroupData = await fetchMinterGroupMembers() const minterGroupData = await fetchMinterGroupMembers()
const minterGroupAddresses = minterGroupData.map(m => m.member) const minterGroupAddresses = minterGroupData.map(m => m.member)
const userAddress = userState.accountAddress const userAddress = userState.accountAddress
@ -759,6 +874,7 @@ const publishCard = async (cardIdentifierPrefix) => {
alert("You are already a Minter and cannot publish a new card!") alert("You are already a Minter and cannot publish a new card!")
return return
} }
const header = document.getElementById("card-header").value.trim() const header = document.getElementById("card-header").value.trim()
const content = document.getElementById("card-content").value.trim() const content = document.getElementById("card-content").value.trim()
const links = Array.from(document.querySelectorAll(".card-link")) const links = Array.from(document.querySelectorAll(".card-link"))
@ -770,8 +886,27 @@ const publishCard = async (cardIdentifierPrefix) => {
return return
} }
const cardIdentifier = isExistingCard ? existingCardIdentifier : `${cardIdentifierPrefix}-${await uid()}` if (isExistingCard) {
const pollName = `${cardIdentifier}-poll` if (!existingCardData || Object.keys(existingCardData).length === 0) {
const fetched = await fetchExistingCard(cardIdentifierPrefix)
if (fetched) {
existingCardData = fetched
} else {
console.warn("fetchExistingCard returned null. Possibly no existing card found.")
}
}
}
const cardIdentifier = isExistingCard && existingCardIdentifier
? existingCardIdentifier
: `${cardIdentifierPrefix}-${await uid()}`
let existingPollName
if (existingCardData && existingCardData.poll) {
existingPollName = existingCardData.poll
}
const pollName = existingPollName || `${cardIdentifier}-poll`
const pollDescription = `Mintership Board Poll for ${userState.accountName}` const pollDescription = `Mintership Board Poll for ${userState.accountName}`
const cardData = { const cardData = {
@ -781,16 +916,16 @@ const publishCard = async (cardIdentifierPrefix) => {
creator: userState.accountName, creator: userState.accountName,
creatorAddress: userState.accountAddress, creatorAddress: userState.accountAddress,
timestamp: Date.now(), timestamp: Date.now(),
poll: pollName, poll: pollName // either the existing poll or a new one
} }
try { try {
let base64CardData = await objectToBase64(cardData) let base64CardData = await objectToBase64(cardData)
if (!base64CardData) { if (!base64CardData) {
console.log(`initial base64 object creation with objectToBase64 failed, using btoa...`) console.log(`initial base64 object creation with objectToBase64 failed, using btoa...`)
base64CardData = btoa(JSON.stringify(cardData)) base64CardData = btoa(JSON.stringify(cardData))
} }
await qortalRequest({ await qortalRequest({
action: "PUBLISH_QDN_RESOURCE", action: "PUBLISH_QDN_RESOURCE",
name: userState.accountName, name: userState.accountName,
@ -799,7 +934,7 @@ const publishCard = async (cardIdentifierPrefix) => {
data64: base64CardData, data64: base64CardData,
}) })
if (!isExistingCard){ if (!isExistingCard || !existingPollName) {
await qortalRequest({ await qortalRequest({
action: "CREATE_POLL", action: "CREATE_POLL",
pollName, pollName,
@ -807,26 +942,33 @@ const publishCard = async (cardIdentifierPrefix) => {
pollOptions: ['Yes, No'], pollOptions: ['Yes, No'],
pollOwnerAddress: userState.accountAddress, pollOwnerAddress: userState.accountAddress,
}) })
alert("Card and poll published successfully!") if (!isExistingCard) {
alert("Card and poll published successfully!")
} else {
alert("Existing card updated, and new poll created (since existing poll was missing)!")
}
} else {
alert("Card updated successfully! (No poll updates possible)")
} }
if (isExistingCard){ if (isExistingCard) {
alert("Card Updated Successfully! (No poll updates possible)")
isExistingCard = false isExistingCard = false
existingCardData = {}
} }
document.getElementById("publish-card-form").reset() document.getElementById("publish-card-form").reset()
document.getElementById("publish-card-view").style.display = "none" document.getElementById("publish-card-view").style.display = "none"
document.getElementById("cards-container").style.display = "flex" document.getElementById("cards-container").style.display = "flex"
await loadCards(minterCardIdentifierPrefix) await loadCards(minterCardIdentifierPrefix)
} catch (error) { } catch (error) {
console.error("Error publishing card or poll:", error) console.error("Error publishing card or poll:", error)
alert("Failed to publish card and poll.") alert("Failed to publish card and poll.")
} }
} }
let globalVoterMap = new Map() let globalVoterMap = new Map()
const processPollData= async (pollData, minterGroupMembers, minterAdmins, creator, cardIdentifier) => { const processPollData= async (pollData, minterGroupMembers, minterAdmins, creator, cardIdentifier) => {
@ -1193,6 +1335,7 @@ const toggleComments = async (cardIdentifier) => {
const commentButton = document.getElementById(`comment-button-${cardIdentifier}`) const commentButton = document.getElementById(`comment-button-${cardIdentifier}`)
if (!commentsSection || !commentButton) return if (!commentsSection || !commentButton) return
const count = commentButton.dataset.commentCount const count = commentButton.dataset.commentCount
const isHidden = (commentsSection.style.display === 'none' || !commentsSection.style.display) const isHidden = (commentsSection.style.display === 'none' || !commentsSection.style.display)
@ -1469,7 +1612,7 @@ const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) =>
// fetch all final KICK/BAN tx // fetch all final KICK/BAN tx
const { finalKickTxs, finalBanTxs } = await fetchAllKickBanTxData() const { finalKickTxs, finalBanTxs } = await fetchAllKickBanTxData()
const { finalInviteTxs, pendingInviteTxs } = await fetchAllInviteTransactions() const { finalInviteTxs, pendingInviteTxs } = await fetchAllInviteTransactions()
// check if there's a final (non-pending) KICK or BAN for this user // check if there's a KICK or BAN for this user.
const priorKick = finalKickTxs.some(tx => tx.member === minterAddress) const priorKick = finalKickTxs.some(tx => tx.member === minterAddress)
const priorBan = finalBanTxs.some(tx => tx.offender === minterAddress) const priorBan = finalBanTxs.some(tx => tx.offender === minterAddress)
const existingInvite = finalInviteTxs.some(tx => tx.invitee === minterAddress) const existingInvite = finalInviteTxs.some(tx => tx.invitee === minterAddress)
@ -1480,10 +1623,12 @@ const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) =>
// build the normal invite button & groupApprovalHtml // build the normal invite button & groupApprovalHtml
let inviteButtonHtml = "" let inviteButtonHtml = ""
if (existingInvite || pendingInvite){ if (existingInvite || pendingInvite){
console.warn(`There is an EXISTING INVITE for this user! No invite button being created... existing: (${existingInvite}, pending: ${pendingInvite})`) console.warn(`There is an EXISTING or PENDING INVITE for this user! No invite button being created... existing: (${existingInvite}, pending: ${pendingInvite})`)
inviteButtonHtml = '' inviteButtonHtml = ''
} else {
inviteButtonHtml = isSomeTypaAdmin ? createInviteButtonHtml(creator, cardIdentifier) : ""
} }
inviteButtonHtml = isSomeTypaAdmin ? createInviteButtonHtml(creator, cardIdentifier) : ""
const groupApprovalHtml = await checkGroupApprovalAndCreateButton(minterAddress, cardIdentifier, "GROUP_INVITE") const groupApprovalHtml = await checkGroupApprovalAndCreateButton(minterAddress, cardIdentifier, "GROUP_INVITE")
// if user had no prior KICK/BAN // if user had no prior KICK/BAN
@ -1903,7 +2048,7 @@ const getNewestCommentTimestamp = async (cardIdentifier) => {
} }
// Create the overall Minter Card HTML ----------------------------------------------- // Create the overall Minter Card HTML -----------------------------------------------
const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCount, cardUpdatedTime, bgColor, address) => { const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCount, cardUpdatedTime, bgColor, address, isExistingMinter=false) => {
const { header, content, links, creator, creatorAddress, timestamp, poll } = cardData const { header, content, links, creator, creatorAddress, timestamp, poll } = cardData
const formattedDate = cardUpdatedTime ? new Date(cardUpdatedTime).toLocaleString() : new Date(timestamp).toLocaleString() const formattedDate = cardUpdatedTime ? new Date(cardUpdatedTime).toLocaleString() : new Date(timestamp).toLocaleString()
const avatarHtml = await getMinterAvatar(creator) const avatarHtml = await getMinterAvatar(creator)
@ -1919,7 +2064,7 @@ const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCoun
createModal('links') createModal('links')
createModal('poll-details') createModal('poll-details')
const inviteButtonHtml = await checkAndDisplayInviteButton(adminYes, creator, cardIdentifier) const inviteButtonHtml = isExistingMinter ? "" : await checkAndDisplayInviteButton(adminYes, creator, cardIdentifier)
let inviteHtmlAdd = (inviteButtonHtml) ? inviteButtonHtml : '' let inviteHtmlAdd = (inviteButtonHtml) ? inviteButtonHtml : ''
let finalBgColor = bgColor let finalBgColor = bgColor
@ -1935,6 +2080,9 @@ const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCoun
finalBgColor = "rgba(1, 65, 39, 0.41)"; // or any green you want finalBgColor = "rgba(1, 65, 39, 0.41)"; // or any green you want
} else if (userVote === 1) { } else if (userVote === 1) {
finalBgColor = "rgba(107, 3, 3, 0.3)"; // or any red you want finalBgColor = "rgba(107, 3, 3, 0.3)"; // or any red you want
} else if (isExistingMinter){
finalBgColor = "rgb(99, 99, 99)"
invitedText = `<h4 style="color:rgb(135, 55, 16); margin-bottom: 0.5em;">EXISTING MINTER</h4>`
} else if (hasMinterInvite) { } else if (hasMinterInvite) {
// If so, override background color & add an "INVITED" label // If so, override background color & add an "INVITED" label
finalBgColor = "black"; finalBgColor = "black";

View File

@ -1,4 +1,4 @@
const Q_MINTERSHIP_VERSION = "1.06" const Q_MINTERSHIP_VERSION = "1.06.4"
const messageIdentifierPrefix = `mintership-forum-message` const messageIdentifierPrefix = `mintership-forum-message`
const messageAttachmentIdentifierPrefix = `mintership-forum-attachment` const messageAttachmentIdentifierPrefix = `mintership-forum-attachment`

View File

@ -224,6 +224,12 @@ const getUserAddress = async () => {
} }
const getAddressInfo = async (address) => { const getAddressInfo = async (address) => {
const qortalAddressPattern = /^Q[A-Za-z0-9]{33}$/ // Q + 33 almum = 34 total length
if (!qortalAddressPattern.test(address)) {
console.warn(`Not a valid Qortal address format, returning same thing that was passed to not break other functions: ${address}`)
return address
}
try { try {
const response = await fetch (`${baseUrl}/addresses/${address}`, { const response = await fetch (`${baseUrl}/addresses/${address}`, {
headers: { 'Accept': 'application/json' }, headers: { 'Accept': 'application/json' },
@ -786,18 +792,20 @@ const searchSimple = async (service, identifier, name, limit=1500, offset=0, roo
if (name && !identifier && !room) { if (name && !identifier && !room) {
console.log('name only searchSimple', name) console.log('name only searchSimple', name)
urlSuffix = `service=${service}&name=${name}&limit=${limit}&prefix=true&reverse=${reverse}` urlSuffix = `service=${service}&name=${name}&limit=${limit}&prefix=true&reverse=${reverse}&after=${after}`
console.log(`urlSuffix used: ${urlSuffix}`)
} else if (!name && identifier && !room) { } else if (!name && identifier && !room) {
console.log('identifier only searchSimple', identifier) console.log('identifier only searchSimple', identifier)
urlSuffix = `service=${service}&identifier=${identifier}&limit=${limit}&prefix=true&reverse=${reverse}` urlSuffix = `service=${service}&identifier=${identifier}&limit=${limit}&prefix=true&reverse=${reverse}&after=${after}`
console.log(`urlSuffix used: ${urlSuffix}`)
} else if (!name && !identifier && !room) { } else if (!name && !identifier && !room) {
console.error(`name: ${name} AND identifier: ${identifier} not passed. Must include at least one...`) console.error(`name: ${name} AND identifier: ${identifier} not passed. Must include at least one...`)
return null return null
} else { } else {
console.log(`final searchSimple params = service: '${service}', identifier: '${identifier}', name: '${name}', limit: '${limit}', offset: '${offset}', room: '${room}', reverse: '${reverse}'`) console.log(`final searchSimple params = service: '${service}', identifier: '${identifier}', name: '${name}', limit: '${limit}', offset: '${offset}', room: '${room}', reverse: '${reverse}', after: ${after}`)
} }
const response = await fetch(`${baseUrl}/arbitrary/resources/searchsimple?${urlSuffix}`, { const response = await fetch(`${baseUrl}/arbitrary/resources/searchsimple?${urlSuffix}`, {
@ -1395,16 +1403,16 @@ const createGroupInviteTransaction = async (recipientAddress, adminPublicKey, gr
} }
} }
const createGroupKickTransaction = async (recipientAddress, adminPublicKey, groupId=694, member, reason='Kicked by admins', txGroupId, fee) => { const createGroupKickTransaction = async (adminPublicKey, groupId=694, member, reason='Kicked by admins', txGroupId=694, fee=0.01) => {
try { try {
// Fetch account reference correctly // Fetch account reference correctly
const accountInfo = await getAddressInfo(recipientAddress) const accountInfo = await getAddressInfo(member)
const accountReference = accountInfo.reference const accountReference = accountInfo.reference
// Validate inputs before making the request // Validate inputs before making the request
if (!adminPublicKey || !accountReference || !recipientAddress) { if (!adminPublicKey || !accountReference || !member) {
throw new Error("Missing required parameters for group invite transaction.") throw new Error("Missing required parameters for group kick transaction.")
} }
const payload = { const payload = {
@ -1412,11 +1420,10 @@ const createGroupKickTransaction = async (recipientAddress, adminPublicKey, grou
reference: accountReference, reference: accountReference,
fee, fee,
txGroupId, txGroupId,
recipient: null,
adminPublicKey, adminPublicKey,
groupId: groupId, groupId,
member: member || recipientAddress, member,
reason: reason reason
} }
console.log("Sending GROUP_KICK transaction payload:", payload) console.log("Sending GROUP_KICK transaction payload:", payload)

View File

@ -141,7 +141,7 @@
<div class="row"> <div class="row">
<div class="col-12 col-lg-4"> <div class="col-12 col-lg-4">
<div class="card-wrapper" style="justify-content:center; align: center; align-text: center;"> <div class="card-wrapper" style="justify-content:center;">
<div class="icon-wrapper"> <div class="icon-wrapper">
<span class="mbr-iconfont mbr-iconfont-btn mbri-file" style="color:aliceblue;"></span> <span class="mbr-iconfont mbr-iconfont-btn mbri-file" style="color:aliceblue;"></span>
</div> </div>
@ -200,14 +200,14 @@
<div class="row"> <div class="row">
<div class="col-12 col-lg-7 card"> <div class="col-12 col-lg-7 card">
<div class="title-wrapper"> <div class="title-wrapper">
<h2 class="mbr-section-title mbr-fonts-style display-2"> <h2 class="mbr-section-title mbr-fonts-style display-2 version">
v1.06beta 01-31-2025</h2> </h2>
</div> </div>
</div> </div>
<div class="col-12 col-lg-5 card"> <div class="col-12 col-lg-5 card">
<div class="text-wrapper"> <div class="text-wrapper">
<p class="mbr-text mbr-fonts-style display-7"> <p class="mbr-text mbr-fonts-style display-7">
<b><u>v1.06b Fixes</u></b>- <b>EMERGENCY UPDATE </b> - See post in the <a href="MINTERSHIP-FORUM">FORUM</a> for RELEASE NOTES, This is an emergency update that is meant to prevent the issue that took place yesterday and ended up stalling quite a few nodes. This means that Q-Mintership should be the ONLY APP UTILIZED FOR THE FUNCTIONALITY IT PROVIDES. <b class="version"><u>v1.06.4b</u></b>- <b>various improvements</b> - See post in the <a href="MINTERSHIP-FORUM">FORUM</a> for RELEASE NOTES.
</p> </p>
</div> </div>
</div> </div>