fix(admin): issue #131 — dead callback buttons prod_district_/admin_users + no-op placeholders

- prod_district_: pipe-delimited format (multi-word names safe) + handleDistrictBack shows categories (admin sees disabled locations via getLocationsByCountryAndCityAdmin)
- admin_users: handleUserListBack (viewUserPage(0) edit)
- current_page/current_quantity/no_action: no-op exact routes (no 'No handler' warns)
- Audit: all 78 generated callbacks cross-checked — no overlaps, none missing
- 48 tests pass; bump v1.2.6
This commit is contained in:
NW
2026-08-05 12:46:37 +01:00
parent 61c7a90aeb
commit a5ea99b39b
8 changed files with 101 additions and 6 deletions

View File

@@ -9,10 +9,16 @@
## Current Version
**v1.2.5** — 2026-08-05
**v1.2.6** — 2026-08-05
## Changelog
### v1.2.6 — 2026-08-05
- **fix**: dead admin callback buttons — `prod_district_` (Back to Categories after adding category, pipe-delimited format) and `admin_users` (Back to User List) now registered
- **fix**: no-op placeholder buttons (`current_page`, `current_quantity`, `no_action`) no longer log "No handler" warns
- **fix**: `getLocationsByCountryAndCityAdmin` added (admin sees disabled locations in nav)
- **audit**: all 78 generated callback_data cross-checked against registered routes — no overlaps, none missing
### v1.2.5 — 2026-08-05
- **fix**: crypto deposit error — handleDepositInstruction/handleDepositSelectWallet use editOrSendCallback (photo-safe fallback, fixes "no text in the message to edit" 400)
- **fix**: chat clutter — lastInlineMessageId tracked in showProducts/showProfile/showBalance/showPurchases and deleted in resetUserContext

View File

@@ -28,7 +28,7 @@
<svg class="sa-icon sa-thin">
<use href="/icons/sprite.svg#tag"></use>
</svg>
<span class="fs-xs opacity-70">v1.2.5</span>
<span class="fs-xs opacity-70">v1.2.6</span>
</div>
</div>
</aside>
@@ -43,10 +43,10 @@
</div>
<div class="modal-body">
<div class="alert alert-info mb-3">
<strong>Current:</strong> v1.2.5 &middot; 2026-08-05
<strong>Current:</strong> v1.2.6 &middot; 2026-08-05
</div>
<h6 class="fw-bold mb-2">v1.2.5 <span class="text-muted fs-sm">&mdash; 2026-08-05</span></h6>
<h6 class="fw-bold mb-2">v1.2.6 <span class="text-muted fs-sm">&mdash; 2026-08-05</span></h6>
<ul class="small mb-3">
<li><span class="badge bg-info">refactor</span> Removed deposit amount-selection step; deposit_wallet_ now goes directly to Mercuryo instructions</li>
<li><span class="badge bg-primary">feat</span> Updated Mercuryo button text to include VISA/Mastercard branding in all locales</li>

View File

@@ -184,6 +184,26 @@ export default class AdminUserHandler {
}
}
static async handleUserListBack(callbackQuery) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
const chatId = callbackQuery.message.chat.id;
try {
const {text, markup} = await this.viewUserPage(0);
await bot.editMessageText(text, {
chat_id: chatId,
message_id: callbackQuery.message.message_id,
reply_markup: markup,
parse_mode: 'HTML'
});
} catch (e) {
return;
}
}
static async handleViewUser(callbackQuery) {
if (!isAdmin(callbackQuery.from.id)) return;

View File

@@ -44,7 +44,7 @@ export default class CategoryAddHandler {
inline_keyboard: [[
{
text: '« Back to Categories',
callback_data: `prod_district_${location.country}_${location.city}_${location.district}`
callback_data: `prod_district_${encodeURIComponent(location.country)}|${encodeURIComponent(location.city)}|${encodeURIComponent(location.district)}`
}
]]
}
@@ -83,7 +83,7 @@ export default class CategoryAddHandler {
message_id: callbackQuery.message.message_id,
reply_markup: {
inline_keyboard: [[
{text: '❌ Cancel', callback_data: `prod_district_${location.country}_${location.city}_${location.district}`}
{text: '❌ Cancel', callback_data: `prod_district_${encodeURIComponent(location.country)}|${encodeURIComponent(location.city)}|${encodeURIComponent(location.district)}`}
]]
}
}

View File

@@ -45,6 +45,51 @@ export default class DistrictHandler {
}
}
static async handleDistrictBack(callbackQuery) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
const payload = callbackQuery.data.replace('prod_district_', '');
const [country, city, district] = payload.split('|').map(decodeURIComponent);
try {
const all = await LocationService.getLocationsByCountryAndCityAdmin(country, city);
const location = all.find(l => l.district === district);
if (!location) {
throw new Error('Location not found');
}
const categories = await CategoryService.getCategoriesByLocationId(location.id);
const keyboard = {
inline_keyboard: [
...categories.map(cat => [{
text: cat.name,
callback_data: `prod_category_${location.id}_${cat.id}`
}]),
[{text: ' Add Category', callback_data: `add_category_${location.id}`}],
[{text: '« Back', callback_data: `prod_city_${encodeURIComponent(country)}|${encodeURIComponent(city)}`}]
]
};
await bot.editMessageText(
'📦 Select or add category:',
{
chat_id: chatId,
message_id: messageId,
reply_markup: keyboard
}
);
} catch (error) {
logger.error({ err: error }, 'Error in handleDistrictBack');
await editOrSendCallback(callbackQuery, 'Error loading categories. Please try again.');
}
}
static async handleDistrictSelection(callbackQuery) {
if (!isAdmin(callbackQuery.from.id)) {
return;

View File

@@ -16,6 +16,7 @@ export default {
handleCountrySelection: NavigationHandler.handleCountrySelection,
handleCitySelection: DistrictHandler.handleCitySelection,
handleDistrictSelection: DistrictHandler.handleDistrictSelection,
handleDistrictBack: DistrictHandler.handleDistrictBack,
handleCategoryInput: CategoryAddHandler.handleCategoryInput,
handleAddCategory: CategoryAddHandler.handleAddCategory,
handleCategoryUpdate: CategoryEditHandler.handleCategoryUpdate,

View File

@@ -55,6 +55,14 @@ export function registerRoutes() {
await userHandler.handleSetLanguage(cq);
});
// === No-op Placeholder Buttons ===
const noopActions = ['current_page', 'current_quantity', 'no_action'];
for (const action of noopActions) {
callbackRouter.registerExact(action, async () => {
// Placeholder button — nothing to do
});
}
// === Text Commands ===
messageRouter.registerText('keyboard.products', async (msg) => {
if (shouldDebounceMainMenu(msg.chat.id, 'keyboard.products')) return;
@@ -207,6 +215,10 @@ export function registerRoutes() {
logDebug(cq.data, 'handleBackToWalletTypes');
await adminWalletsHandler.handleBackToWalletTypes(cq);
});
callbackRouter.registerExact('admin_users', async (cq) => {
logDebug(cq.data, 'handleUserListBack');
await adminUserHandler.handleUserListBack(cq);
});
// === Prefix Callback Routes ===
callbackRouter.registerPrefix('set_country_', async (cq) => {
@@ -297,6 +309,10 @@ export function registerRoutes() {
logDebug(cq.data, 'handleDistrictSelection');
await productHandler.handleDistrictSelection(cq);
});
callbackRouter.registerPrefix('prod_district_', async (cq) => {
logDebug(cq.data, 'handleDistrictBack');
await productHandler.handleDistrictBack(cq);
});
callbackRouter.registerPrefix('add_category_', async (cq) => {
logDebug(cq.data, 'handleAddCategory');
await productHandler.handleAddCategory(cq);

View File

@@ -27,6 +27,13 @@ class LocationService {
);
}
static async getLocationsByCountryAndCityAdmin(country, city) {
return await db.allAsync(
'SELECT id, country, city, district FROM locations WHERE country = ? AND city = ? ORDER BY district',
[country, city]
);
}
static async getLocation(country, city, district) {
try {
const location = await db.getAsync(