Fixed issue with display of pending invites on AdminTools page.
This commit is contained in:
parent
fe230a91d3
commit
6f459d7e0a
@ -584,7 +584,7 @@ body {
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
margin: 20px auto; /* center horizontally */
|
margin: 20px auto; /* center horizontally */
|
||||||
max-width: 600px; /* limit width */
|
/* max-width: 600px; */
|
||||||
color: #ddd; /* text color */
|
color: #ddd; /* text color */
|
||||||
text-align: center;
|
text-align: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -596,7 +596,7 @@ body {
|
|||||||
background-color:#000000;
|
background-color:#000000;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
font-size: 1.8rem;
|
font-size: 1.8rem;
|
||||||
color: #4d0000;
|
color: #fff3f3;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
/* you could style the list items or bullet if you like */
|
/* you could style the list items or bullet if you like */
|
||||||
@ -616,7 +616,17 @@ body {
|
|||||||
background-color: #14161a;
|
background-color: #14161a;
|
||||||
border: 1px solid #8caeb0;
|
border: 1px solid #8caeb0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
color: #5c0101;
|
color: #f19c9c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-form input.invite-input {
|
||||||
|
padding: 1rem;
|
||||||
|
font-size: 2rem;
|
||||||
|
line-height: 2;
|
||||||
|
background-color: #14161a;
|
||||||
|
border: 1px solid #8caeb0;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #dddddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.publish-card-button {
|
.publish-card-button {
|
||||||
@ -707,6 +717,43 @@ body {
|
|||||||
background-color: #281e1e;
|
background-color: #281e1e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.approve-invite-list-button {
|
||||||
|
background-color: rgba(32, 88, 34, 0.554);
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 1vw;
|
||||||
|
padding: 1vh,2vh;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.approve-invite-list-button:hover {
|
||||||
|
background-color: rgba(34, 186, 47, 0.84); /* a darker variant */
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-approvals strong {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-item {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
background-color: rgba(31, 31, 31, 0.595);
|
||||||
|
border: 1px solid #444;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #ccc;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Top row: use flex for horizontal arrangement */
|
||||||
|
.invite-top-row {
|
||||||
|
display: flex;
|
||||||
|
background-color:#173c52ae;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive Design */
|
/* Responsive Design */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
@ -33,7 +33,7 @@ const loadMinterAdminToolsPage = async () => {
|
|||||||
<div id="tools-submenu" class="tools-submenu">
|
<div id="tools-submenu" class="tools-submenu">
|
||||||
<div class="tools-buttons" style="display: flex; gap: 1em; justify-content: center;">
|
<div class="tools-buttons" style="display: flex; gap: 1em; justify-content: center;">
|
||||||
<button id="toggle-blocklist-button" class="publish-card-button">Add/Remove blockedUsers</button>
|
<button id="toggle-blocklist-button" class="publish-card-button">Add/Remove blockedUsers</button>
|
||||||
<button id="create-group-invite" class="publish-card-button">Create Pending Group Invite</button>
|
<button id="create-group-invite" class="publish-card-button" style="backgroundColor:rgb(82, 114, 145)">Create and Display Pending Group Invites</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="tools-window" class="tools-window" style="margin-top: 2em;">
|
<div id="tools-window" class="tools-window" style="margin-top: 2em;">
|
||||||
@ -55,6 +55,30 @@ const loadMinterAdminToolsPage = async () => {
|
|||||||
<button id="blocklist-remove-button" class="publish-card-button">Remove</button>
|
<button id="blocklist-remove-button" class="publish-card-button">Remove</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="invite-container" class="invite-form" style="display: none; flex-direction: column; padding: 0.75em; align-items: center; justify-content: center;">
|
||||||
|
|
||||||
|
<!-- Existing pending invites display -->
|
||||||
|
<div id="pending-invites-display" class="pending-invites-display" style="margin-bottom: 1em;">
|
||||||
|
<!-- We will fill this dynamically with a list/table of pending invites -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Input for name/address -->
|
||||||
|
<h3 style="margin-top: 0;">Manual Group Invite</h3>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="invite-input"
|
||||||
|
class="invite-input"
|
||||||
|
placeholder="Enter name or address to invite"
|
||||||
|
style="margin-bottom: 1em;"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Button to create the invite transaction -->
|
||||||
|
<div class="invite-button-container publish-card-form">
|
||||||
|
<button id="invite-user-button" class="publish-card-button">Invite User</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -63,10 +87,10 @@ const loadMinterAdminToolsPage = async () => {
|
|||||||
|
|
||||||
document.body.appendChild(mainContent)
|
document.body.appendChild(mainContent)
|
||||||
|
|
||||||
addToolsPageEventListeners()
|
await addToolsPageEventListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
function addToolsPageEventListeners() {
|
const addToolsPageEventListeners= async () => {
|
||||||
document.getElementById("toggle-blocklist-button").addEventListener("click", async () => {
|
document.getElementById("toggle-blocklist-button").addEventListener("click", async () => {
|
||||||
const container = document.getElementById("blocklist-container")
|
const container = document.getElementById("blocklist-container")
|
||||||
// toggle show/hide
|
// toggle show/hide
|
||||||
@ -116,6 +140,32 @@ function addToolsPageEventListeners() {
|
|||||||
alert(`"${nameToRemove}" removed from the block list (if it was present).`)
|
alert(`"${nameToRemove}" removed from the block list (if it was present).`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
document.getElementById("invite-user-button").addEventListener("click", async () => {
|
||||||
|
const inviteInput = document.getElementById("invite-input")
|
||||||
|
const nameOrAddress = inviteInput.value.trim()
|
||||||
|
if (!nameOrAddress) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
// We'll call some function handleManualInvite(nameOrAddress)
|
||||||
|
await handleManualInvite(nameOrAddress)
|
||||||
|
inviteInput.value = ""
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error inviting user:", err)
|
||||||
|
alert("Failed to invite user.")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
document.getElementById("create-group-invite").addEventListener("click", async () => {
|
||||||
|
const inviteContainer = document.getElementById("invite-container")
|
||||||
|
// Toggle display
|
||||||
|
inviteContainer.style.display = (inviteContainer.style.display === "none" ? "flex" : "none")
|
||||||
|
// If showing, load the pending invites
|
||||||
|
if (inviteContainer.style.display === "flex") {
|
||||||
|
const pendingInvites = await fetchPendingInvites()
|
||||||
|
await displayPendingInviteDetails(pendingInvites)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const displayBlockList = (blockedNames) => {
|
const displayBlockList = (blockedNames) => {
|
||||||
@ -131,4 +181,139 @@ const displayBlockList = (blockedNames) => {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchPendingInvites = async () => {
|
||||||
|
try {
|
||||||
|
const { finalInviteTxs, pendingInviteTxs } = await fetchAllInviteTransactions()
|
||||||
|
return pendingInviteTxs
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching pending invites:", err)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleManualInvite = async (nameOrAddress) => {
|
||||||
|
const addressInfo = await getAddressInfo(nameOrAddress)
|
||||||
|
let address = addressInfo.address
|
||||||
|
if (addressInfo && address) {
|
||||||
|
console.log(`address is ${address}`)
|
||||||
|
} else {
|
||||||
|
// it might be a Qortal name => getNameInfo
|
||||||
|
const nameData = await getNameInfo(nameOrAddress)
|
||||||
|
if (!nameData || !nameData.owner) {
|
||||||
|
throw new Error(`Cannot find valid address for ${nameOrAddress}`)
|
||||||
|
}
|
||||||
|
address = nameData.owner
|
||||||
|
}
|
||||||
|
|
||||||
|
const adminPublicKey = await getPublicKeyByName(userState.accountName)
|
||||||
|
const timeToLive = 864000 // e.g. 10 days in seconds
|
||||||
|
const fee = 0.01
|
||||||
|
let txGroupId = 694
|
||||||
|
|
||||||
|
// build the raw invite transaction
|
||||||
|
const rawInviteTransaction = await createGroupInviteTransaction(
|
||||||
|
address,
|
||||||
|
adminPublicKey,
|
||||||
|
694,
|
||||||
|
address,
|
||||||
|
timeToLive,
|
||||||
|
txGroupId,
|
||||||
|
fee
|
||||||
|
)
|
||||||
|
|
||||||
|
// sign
|
||||||
|
const signedTransaction = await qortalRequest({
|
||||||
|
action: "SIGN_TRANSACTION",
|
||||||
|
unsignedBytes: rawInviteTransaction
|
||||||
|
})
|
||||||
|
if (!signedTransaction) {
|
||||||
|
throw new Error("SIGN_TRANSACTION returned null. Possibly user canceled or an older UI?")
|
||||||
|
}
|
||||||
|
|
||||||
|
// process
|
||||||
|
const processResponse = await processTransaction(signedTransaction)
|
||||||
|
if (!processResponse) {
|
||||||
|
throw new Error("Failed to process transaction. Possibly canceled or error from Qortal Core.")
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(`Invite transaction submitted for ${nameOrAddress}. Wait for confirmation.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const displayPendingInviteDetails = async (pendingInvites) => {
|
||||||
|
const invitesContainer = document.getElementById('pending-invites-display')
|
||||||
|
if (!pendingInvites || pendingInvites.length === 0) {
|
||||||
|
invitesContainer.innerHTML = "<p>No pending invites found.</p>"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = `<h4>Current Pending Invites:</h4><div class="pending-invites-list">`
|
||||||
|
|
||||||
|
for (const inviteTx of pendingInvites) {
|
||||||
|
const inviteeAddress = inviteTx.invitee
|
||||||
|
const dateStr = new Date(inviteTx.timestamp).toLocaleString()
|
||||||
|
let inviteeName = ""
|
||||||
|
const txSig = inviteTx.signature
|
||||||
|
const creatorName = await getNameFromAddress(inviteTx.creatorAddress)
|
||||||
|
if (!creatorName) {
|
||||||
|
creatorName = inviteTx.creatorAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// fetch the name from address, if it fails we keep it blank or fallback to the address
|
||||||
|
inviteeName = await getNameFromAddress(inviteeAddress)
|
||||||
|
if (!inviteeName || inviteeName === inviteeAddress) {
|
||||||
|
inviteeName = inviteeAddress // fallback
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
inviteeName = inviteeAddress // fallback if getName fails
|
||||||
|
}
|
||||||
|
|
||||||
|
const approvalSearchResults = await searchTransactions({
|
||||||
|
txTypes: ['GROUP_APPROVAL'],
|
||||||
|
confirmationStatus: 'CONFIRMED',
|
||||||
|
limit: 0,
|
||||||
|
reverse: false,
|
||||||
|
offset: 0,
|
||||||
|
startBlock: 1990000,
|
||||||
|
blockLimit: 0,
|
||||||
|
txGroupId: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const approvals = approvalSearchResults.filter(
|
||||||
|
(approvalTx) => approvalTx.pendingSignature === txSig
|
||||||
|
)
|
||||||
|
|
||||||
|
const { tableHtml, approvalCount } = await buildApprovalTableHtml(approvals, getNameFromAddress)
|
||||||
|
const finalTable = approvals.length > 0 ? tableHtml : "<p>No Approvals Found</p>"
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<div class="invite-item">
|
||||||
|
<div class="invite-top-row">
|
||||||
|
<span><strong>Invite Tx</strong>:<p style="color:lightblue"> ${inviteTx.signature.slice(0, 8)}...</p></span>
|
||||||
|
<span> <strong>Invitee</strong>:<p style="color:lightblue"> ${inviteeName}</p></span>
|
||||||
|
<span> <strong>Date</strong>:<p style="color:lightblue"> ${dateStr}</p></span>
|
||||||
|
<span> <strong>CreatorName</strong>:<p style="color:lightblue"> ${creatorName}</p></span>
|
||||||
|
<span> <strong>Total Approvals</strong>:<p style="color:lightblue"> ${approvalCount}</p></span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- Next line for approvals -->
|
||||||
|
<div class="invite-approvals">
|
||||||
|
<strong>Existing Approvals:</strong>
|
||||||
|
${finalTable}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="approve-invite-list-button"
|
||||||
|
onclick="handleGroupApproval('${inviteTx.signature}')"
|
||||||
|
>
|
||||||
|
Approve Invite
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
html += "</div>"
|
||||||
|
invitesContainer.innerHTML = html
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1652,10 +1652,8 @@ const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const findPendingApprovalTxForAddress = async (address, txType, limit = 0, offset = 0) => {
|
const findPendingTxForAddress = async (address, txType, limit = 0, offset = 0) => {
|
||||||
// 1) Fetch all pending transactions
|
|
||||||
const pendingTxs = await searchPendingTransactions(limit, offset, false)
|
const pendingTxs = await searchPendingTransactions(limit, offset, false)
|
||||||
// if a txType is passed, return the results related to that type, if not, then return any pending tx of the potential types.
|
|
||||||
let relevantTypes
|
let relevantTypes
|
||||||
if (txType) {
|
if (txType) {
|
||||||
relevantTypes = new Set([txType])
|
relevantTypes = new Set([txType])
|
||||||
@ -1710,15 +1708,15 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
|
|||||||
blockLimit: 0,
|
blockLimit: 0,
|
||||||
txGroupId: 0
|
txGroupId: 0
|
||||||
})
|
})
|
||||||
const pendingApprovals = await findPendingApprovalTxForAddress(address, transactionType, 0, 0)
|
const pendingTxs = await findPendingTxForAddress(address, transactionType, 0, 0)
|
||||||
let isSomeTypaAdmin = userState.isAdmin || userState.isMinterAdmin
|
let isSomeTypaAdmin = userState.isAdmin || userState.isMinterAdmin
|
||||||
// If no pending transaction found, return null
|
// If no pending transaction found, return null
|
||||||
if (!pendingApprovals || pendingApprovals.length === 0) {
|
if (!pendingTxs || pendingTxs.length === 0) {
|
||||||
console.warn("no pending approval transactions found, returning null...")
|
console.warn("no pending transactions found, returning null...")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const txSig = pendingApprovals[0].signature
|
const txSig = pendingTxs[0].signature
|
||||||
// Find the relevant signature. (First approval)
|
// Find the relevant signature. (signature of the issued transaction pending.)
|
||||||
const relevantApprovals = approvalSearchResults.filter(
|
const relevantApprovals = approvalSearchResults.filter(
|
||||||
(approvalTx) => approvalTx.pendingSignature === txSig
|
(approvalTx) => approvalTx.pendingSignature === txSig
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const Q_MINTERSHIP_VERSION = "1.06.4"
|
const Q_MINTERSHIP_VERSION = "1.06.5"
|
||||||
|
|
||||||
const messageIdentifierPrefix = `mintership-forum-message`
|
const messageIdentifierPrefix = `mintership-forum-message`
|
||||||
const messageAttachmentIdentifierPrefix = `mintership-forum-attachment`
|
const messageAttachmentIdentifierPrefix = `mintership-forum-attachment`
|
||||||
|
@ -1354,7 +1354,7 @@ const processTransaction = async (signedTransaction) => {
|
|||||||
|
|
||||||
// Create a group invite transaction. This will utilize a default timeToLive (which is how long the tx will be alive, not the time until it IS live...) of 10 days in seconds, as the legacy UI has a bug that doesn't display invites older than 10 days.
|
// Create a group invite transaction. This will utilize a default timeToLive (which is how long the tx will be alive, not the time until it IS live...) of 10 days in seconds, as the legacy UI has a bug that doesn't display invites older than 10 days.
|
||||||
// We will also default to the MINTER group for groupId, AFTER the GROUP_APPROVAL changes, the txGroupId will need to be set for tx that require approval.
|
// We will also default to the MINTER group for groupId, AFTER the GROUP_APPROVAL changes, the txGroupId will need to be set for tx that require approval.
|
||||||
const createGroupInviteTransaction = async (recipientAddress, adminPublicKey, groupId=694, invitee, timeToLive, txGroupId, fee) => {
|
const createGroupInviteTransaction = async (recipientAddress, adminPublicKey, groupId=694, invitee, timeToLive=0, txGroupId, fee) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch account reference correctly
|
// Fetch account reference correctly
|
||||||
|
@ -196,13 +196,25 @@ const fetchAllInviteTransactions = async () => {
|
|||||||
|
|
||||||
const { finalTx: finalInviteTxs, pendingTx: pendingInviteTxs } = partitionTransactions(allInviteTx)
|
const { finalTx: finalInviteTxs, pendingTx: pendingInviteTxs } = partitionTransactions(allInviteTx)
|
||||||
|
|
||||||
console.log('Final kickTxs:', finalInviteTxs)
|
console.log('Final InviteTxs:', finalInviteTxs)
|
||||||
console.log('Pending kickTxs:', pendingInviteTxs)
|
console.log('Pending InviteTxs:', pendingInviteTxs)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
finalInviteTxs,
|
finalInviteTxs,
|
||||||
pendingInviteTxs,
|
pendingInviteTxs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const findPendingApprovalsForTxSignature = async (txSignature, txType='GROUP_APPROVAL', limit=0, offset=0) => {
|
||||||
|
const pendingTxs = await searchPendingTransactions(limit, offset)
|
||||||
|
|
||||||
|
// Filter only the relevant GROUP_APPROVAL TX referencing txSignature
|
||||||
|
const approvals = pendingTxs.filter(tx =>
|
||||||
|
tx.type === txType && tx.pendingSignature === txSignature
|
||||||
|
)
|
||||||
|
console.log(`approvals found:`,approvals)
|
||||||
|
return approvals
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user