diff --git a/VERSION.md b/VERSION.md
index 66f6850..4ff3f84 100644
--- a/VERSION.md
+++ b/VERSION.md
@@ -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
diff --git a/src/admin/views/partials/app-sidebar.ejs b/src/admin/views/partials/app-sidebar.ejs
index 2186092..5831291 100644
--- a/src/admin/views/partials/app-sidebar.ejs
+++ b/src/admin/views/partials/app-sidebar.ejs
@@ -28,7 +28,7 @@
- v1.2.5
+ v1.2.6
@@ -43,10 +43,10 @@
- Current: v1.2.5 · 2026-08-05
+ Current: v1.2.6 · 2026-08-05
-
v1.2.5 — 2026-08-05
+
v1.2.6 — 2026-08-05
- refactor Removed deposit amount-selection step; deposit_wallet_ now goes directly to Mercuryo instructions
- feat Updated Mercuryo button text to include VISA/Mastercard branding in all locales
diff --git a/src/handlers/adminHandlers/adminUserHandler.js b/src/handlers/adminHandlers/adminUserHandler.js
index f3628c5..9622171 100644
--- a/src/handlers/adminHandlers/adminUserHandler.js
+++ b/src/handlers/adminHandlers/adminUserHandler.js
@@ -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;
diff --git a/src/handlers/adminHandlers/product/categoryAddHandler.js b/src/handlers/adminHandlers/product/categoryAddHandler.js
index ee9add0..6c211d7 100644
--- a/src/handlers/adminHandlers/product/categoryAddHandler.js
+++ b/src/handlers/adminHandlers/product/categoryAddHandler.js
@@ -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)}`}
]]
}
}
diff --git a/src/handlers/adminHandlers/product/districtHandler.js b/src/handlers/adminHandlers/product/districtHandler.js
index 851c39a..e1ce66e 100644
--- a/src/handlers/adminHandlers/product/districtHandler.js
+++ b/src/handlers/adminHandlers/product/districtHandler.js
@@ -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;
diff --git a/src/handlers/adminHandlers/product/index.js b/src/handlers/adminHandlers/product/index.js
index d11158a..9326b39 100644
--- a/src/handlers/adminHandlers/product/index.js
+++ b/src/handlers/adminHandlers/product/index.js
@@ -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,
diff --git a/src/router/routes.js b/src/router/routes.js
index eb5a616..2b78246 100644
--- a/src/router/routes.js
+++ b/src/router/routes.js
@@ -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);
diff --git a/src/services/locationService.js b/src/services/locationService.js
index 5e08cd8..30a6727 100644
--- a/src/services/locationService.js
+++ b/src/services/locationService.js
@@ -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(