Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 80 additions & 4 deletions template/inbox/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ let pollingInterval = null;
let dbReady = false;
let pendingReplyData = null; // Track pending reply for when confirmed
let pendingReplyUid = null; // UID of pending reply command
let pendingStatusUpdateData = null; // Track pending status update notification
let pendingStatusUpdateUid = null; // UID of pending status update command

function escapeSQL(val) {
if (val == null) return 'NULL';
Expand Down Expand Up @@ -1106,6 +1108,23 @@ MDS.init(async (msg) => {
}
return;
}

// Match our pending STATUS_UPDATE notification by UID
if (pendingStatusUpdateUid && msg.data && msg.data.uid === pendingStatusUpdateUid) {
pendingStatusUpdateUid = null;
const btn = document.getElementById('update-status-btn');
if (msg.data.accept && msg.data.result && msg.data.result.status) {
console.log('Status update notification accepted for ref:', pendingStatusUpdateData?.ref);
if (btn) btn.textContent = '✓ Updated · Buyer notified';
setTimeout(() => { if (btn) btn.textContent = 'Update Status'; }, 3000);
} else {
console.log('Status update notification denied/failed');
if (btn) btn.textContent = '✓ Updated';
setTimeout(() => { if (btn) btn.textContent = 'Update Status'; }, 2000);
}
pendingStatusUpdateData = null;
return;
}
}
});

Expand Down Expand Up @@ -1290,6 +1309,66 @@ function setupExportListeners() {
}
}

// ── Silent status update notification to buyer ─────────────────────────────
async function sendStatusUpdateToBuyer(msg, newStatus, updateBtn) {
if (!msg.buyerPublicKey) {
console.log('sendStatusUpdateToBuyer: no buyerPublicKey, skipping');
return;
}

try {
if (!myPublicKey) {
myPublicKey = await getMyPublicKey();
}

const payload = {
type: 'STATUS_UPDATE',
randomid: generateRandomId(),
ref: msg.ref,
status: newStatus,
timestamp: Date.now(),
vendorPublicKey: myPublicKey || ''
};

const encryptResult = await encryptMessage(msg.buyerPublicKey, payload);
if (!encryptResult || !encryptResult.encrypted) {
console.warn('sendStatusUpdateToBuyer: encryption failed');
return;
}

const state = {};
state[99] = encryptResult.encrypted;
const command = 'send address:' + MINIMERCH_ADDRESS + ' amount:0.0001 tokenid:' + TOKEN_IDS.MINIMA + ' state:' + JSON.stringify(state);

pendingStatusUpdateData = { ref: msg.ref, status: newStatus };

MDS.cmd(command, (response) => {
console.log('Status update TX response:', JSON.stringify(response));

if (response && response.pending) {
pendingStatusUpdateUid = response.pendinguid;
if (updateBtn) updateBtn.textContent = '✓ Updated · Pending approval';
setTimeout(() => { if (updateBtn) updateBtn.textContent = 'Update Status'; }, 4000);
return;
}

pendingStatusUpdateData = null;

if (response && response.status) {
console.log('Status update notification sent to buyer for ref:', msg.ref);
if (updateBtn) updateBtn.textContent = '✓ Updated · Buyer notified';
setTimeout(() => { if (updateBtn) updateBtn.textContent = 'Update Status'; }, 3000);
} else {
console.warn('sendStatusUpdateToBuyer: send failed', response?.error);
if (updateBtn) updateBtn.textContent = '✓ Updated';
setTimeout(() => { if (updateBtn) updateBtn.textContent = 'Update Status'; }, 2000);
}
});
} catch (err) {
console.error('sendStatusUpdateToBuyer error:', err);
}
}

// Update status button
function setupStatusUpdateListener() {
const updateStatusBtn = document.getElementById('update-status-btn');
Expand All @@ -1304,11 +1383,8 @@ function setupStatusUpdateListener() {

if (success) {
selectedMessage.status = newStatus;
updateStatusBtn.textContent = '✓ Updated';
setTimeout(() => {
updateStatusBtn.textContent = 'Update Status';
}, 2000);
renderInbox();
sendStatusUpdateToBuyer(selectedMessage, newStatus, updateStatusBtn);
}
});
}
Expand Down
90 changes: 78 additions & 12 deletions template/shop/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,21 +430,43 @@ async function processReplyMessage(coin) {
return;
}

if (decrypted.type !== 'REPLY') {
console.log('Not a reply message, ignoring');
if (decrypted.type !== 'REPLY' && decrypted.type !== 'STATUS_UPDATE') {
console.log('Not a reply or status update, ignoring');
return;
}

// Check for duplicate
const randomid = decrypted.randomid || (decrypted.ref + '_' + decrypted.timestamp);
const stored = await isMessageStored(randomid);
if (stored) {
console.log('Reply already stored, skipping:', randomid);
console.log('Message already stored, skipping:', randomid);
return;
}

console.log('Decrypted reply:', JSON.stringify(decrypted));


console.log('Decrypted message:', JSON.stringify(decrypted));

if (decrypted.type === 'STATUS_UPDATE') {
const statusLabel = decrypted.status
? decrypted.status.charAt(0) + decrypted.status.slice(1).toLowerCase()
: 'Updated';
const message = {
id: Date.now().toString(),
randomid: randomid,
ref: decrypted.ref || 'STATUS-' + Date.now(),
type: 'STATUS_UPDATE',
subject: 'Order status: ' + statusLabel,
message: 'Your order status has been updated to ' + statusLabel,
timestamp: decrypted.timestamp || Date.now(),
coinid: coinid,
read: false,
direction: 'received',
vendorPublicKey: decrypted.vendorPublicKey || decrypted._senderPublicKey || null,
product: decrypted.status || ''
};
await addMessage(message);
return;
}

const message = {
id: Date.now().toString(),
randomid: randomid,
Expand All @@ -460,7 +482,7 @@ async function processReplyMessage(coin) {
vendorPublicKey: decrypted.vendorPublicKey || decrypted._senderPublicKey || null,
vendorAddress: decrypted.vendorAddress || null
};

await addMessage(message);
}

Expand Down Expand Up @@ -1805,16 +1827,20 @@ function renderMessageList(messages, type) {
return messages.map(msg => {
const isReply = msg.type === 'REPLY';
const isBuyerReply = msg.type === 'BUYER_REPLY';
const isStatusUpdate = msg.type === 'STATUS_UPDATE';
const statusIcons = { PENDING: '⏳', PAID: '💳', CONFIRMED: '✅', SHIPPED: '🚚', DELIVERED: '📦' };
const statusIcon = isStatusUpdate ? (statusIcons[msg.product] || '📋') : null;
return `
<div class="message-item ${msg.direction === 'received' && !msg.read ? 'unread' : ''} ${isBuyerReply ? 'buyer-reply' : ''}" data-id="${msg.id}">
<div class="message-icon">${isBuyerReply ? '↩️' : (isReply ? '↩️' : (msg.direction === 'received' ? '📨' : '📤'))}</div>
<div class="message-icon">${isStatusUpdate ? statusIcon : (isBuyerReply ? '↩️' : (isReply ? '↩️' : (msg.direction === 'received' ? '📨' : '📤')))}</div>
<div class="message-preview">
<div class="message-subject">${escapeHtml(msg.subject || msg.product) || 'Order: ' + escapeHtml(msg.ref)}</div>
<div class="message-meta">
<span class="message-ref">${escapeHtml(msg.ref)}</span>
${isBuyerReply ? '<span class="message-type">Your Reply</span>' :
${isStatusUpdate ? '<span class="message-type">Status Update</span>' :
(isBuyerReply ? '<span class="message-type">Your Reply</span>' :
(isReply ? '<span class="message-type">Vendor Reply</span>' :
`<span class="message-amount">$${escapeHtml(msg.amount)} ${escapeHtml(msg.currency)}</span>`)}
`<span class="message-amount">$${escapeHtml(msg.amount)} ${escapeHtml(msg.currency)}</span>`))}
</div>
</div>
<div class="message-time">${formatTime(msg.timestamp)}</div>
Expand All @@ -1826,9 +1852,49 @@ function renderMessageDetail(msg) {
const isReceived = msg.direction === 'received';
const isReply = msg.type === 'REPLY';
const isBuyerReply = msg.type === 'BUYER_REPLY';
const isStatusUpdate = msg.type === 'STATUS_UPDATE';
const canReply = isReply && msg.vendorPublicKey;
const showMarkAsRead = isReceived && !msg.read;


// Status update notification (received)
if (isStatusUpdate) {
const statusIcons = { PENDING: '⏳', PAID: '💳', CONFIRMED: '✅', SHIPPED: '🚚', DELIVERED: '📦' };
const statusIcon = statusIcons[msg.product] || '📋';
const statusLabel = msg.product
? msg.product.charAt(0) + msg.product.slice(1).toLowerCase()
: 'Updated';
return `
<button class="back-btn" id="back-to-list">← Back</button>
<div class="message-header">
<h3>${statusIcon} Order Status Update</h3>
<span class="message-direction">${!msg.read ? '📨 Unread' : '📧 Read'}</span>
</div>

<div class="message-info">
<div class="info-row">
<span class="info-label">Order Ref:</span>
<span class="info-value">${escapeHtml(msg.ref)}</span>
</div>
<div class="info-row">
<span class="info-label">New Status:</span>
<span class="info-value">${statusIcon} ${escapeHtml(statusLabel)}</span>
</div>
<div class="info-row">
<span class="info-label">Time:</span>
<span class="info-value">${new Date(msg.timestamp).toLocaleString()}</span>
</div>
</div>

<div class="reply-content">
<p class="reply-message">${escapeHtml(msg.message)}</p>
</div>

<div class="message-actions">
${showMarkAsRead ? `<button class="mark-read-btn" id="mark-read-btn" data-id="${msg.id}">✓ Mark as Read</button>` : ''}
</div>
`;
}

// Vendor Reply (received)
if (isReply && isReceived) {
return `
Expand Down
21 changes: 21 additions & 0 deletions template/shop/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,27 @@ function processDecryptedMessage(coinid, decrypted, direction) {
vendorAddress: decrypted.vendorAddress || null
};
saveMessageToDb(message);
} else if (decrypted.type === 'STATUS_UPDATE') {
// Vendor status update notification to buyer
let statusLabel = decrypted.status
? decrypted.status.charAt(0) + decrypted.status.slice(1).toLowerCase()
: 'Updated';
let message = {
id: 'svc_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6),
randomid: randomid,
ref: decrypted.ref || 'STATUS-' + Date.now(),
type: 'STATUS_UPDATE',
subject: 'Order status: ' + statusLabel,
product: decrypted.status || '',
message: 'Your order status has been updated to ' + statusLabel,
timestamp: decrypted.timestamp || Date.now(),
coinid: coinid,
read: false,
direction: 'received',
vendorPublicKey: decrypted.vendorPublicKey || decrypted._senderPublicKey || null,
vendorAddress: null
};
saveMessageToDb(message);
}
// Note: We don't store our own sent orders here - that's handled by app.js
});
Expand Down
Loading