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:
@@ -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 · 2026-08-05
|
||||
<strong>Current:</strong> v1.2.6 · 2026-08-05
|
||||
</div>
|
||||
|
||||
<h6 class="fw-bold mb-2">v1.2.5 <span class="text-muted fs-sm">— 2026-08-05</span></h6>
|
||||
<h6 class="fw-bold mb-2">v1.2.6 <span class="text-muted fs-sm">— 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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)}`}
|
||||
]]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user