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:
@@ -9,10 +9,16 @@
|
|||||||
|
|
||||||
## Current Version
|
## Current Version
|
||||||
|
|
||||||
**v1.2.5** — 2026-08-05
|
**v1.2.6** — 2026-08-05
|
||||||
|
|
||||||
## Changelog
|
## 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
|
### 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**: 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
|
- **fix**: chat clutter — lastInlineMessageId tracked in showProducts/showProfile/showBalance/showPurchases and deleted in resetUserContext
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<svg class="sa-icon sa-thin">
|
<svg class="sa-icon sa-thin">
|
||||||
<use href="/icons/sprite.svg#tag"></use>
|
<use href="/icons/sprite.svg#tag"></use>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="fs-xs opacity-70">v1.2.5</span>
|
<span class="fs-xs opacity-70">v1.2.6</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -43,10 +43,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="alert alert-info mb-3">
|
<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>
|
</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">
|
<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-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>
|
<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) {
|
static async handleViewUser(callbackQuery) {
|
||||||
if (!isAdmin(callbackQuery.from.id)) return;
|
if (!isAdmin(callbackQuery.from.id)) return;
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default class CategoryAddHandler {
|
|||||||
inline_keyboard: [[
|
inline_keyboard: [[
|
||||||
{
|
{
|
||||||
text: '« Back to Categories',
|
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,
|
message_id: callbackQuery.message.message_id,
|
||||||
reply_markup: {
|
reply_markup: {
|
||||||
inline_keyboard: [[
|
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) {
|
static async handleDistrictSelection(callbackQuery) {
|
||||||
if (!isAdmin(callbackQuery.from.id)) {
|
if (!isAdmin(callbackQuery.from.id)) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export default {
|
|||||||
handleCountrySelection: NavigationHandler.handleCountrySelection,
|
handleCountrySelection: NavigationHandler.handleCountrySelection,
|
||||||
handleCitySelection: DistrictHandler.handleCitySelection,
|
handleCitySelection: DistrictHandler.handleCitySelection,
|
||||||
handleDistrictSelection: DistrictHandler.handleDistrictSelection,
|
handleDistrictSelection: DistrictHandler.handleDistrictSelection,
|
||||||
|
handleDistrictBack: DistrictHandler.handleDistrictBack,
|
||||||
handleCategoryInput: CategoryAddHandler.handleCategoryInput,
|
handleCategoryInput: CategoryAddHandler.handleCategoryInput,
|
||||||
handleAddCategory: CategoryAddHandler.handleAddCategory,
|
handleAddCategory: CategoryAddHandler.handleAddCategory,
|
||||||
handleCategoryUpdate: CategoryEditHandler.handleCategoryUpdate,
|
handleCategoryUpdate: CategoryEditHandler.handleCategoryUpdate,
|
||||||
|
|||||||
@@ -55,6 +55,14 @@ export function registerRoutes() {
|
|||||||
await userHandler.handleSetLanguage(cq);
|
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 ===
|
// === Text Commands ===
|
||||||
messageRouter.registerText('keyboard.products', async (msg) => {
|
messageRouter.registerText('keyboard.products', async (msg) => {
|
||||||
if (shouldDebounceMainMenu(msg.chat.id, 'keyboard.products')) return;
|
if (shouldDebounceMainMenu(msg.chat.id, 'keyboard.products')) return;
|
||||||
@@ -207,6 +215,10 @@ export function registerRoutes() {
|
|||||||
logDebug(cq.data, 'handleBackToWalletTypes');
|
logDebug(cq.data, 'handleBackToWalletTypes');
|
||||||
await adminWalletsHandler.handleBackToWalletTypes(cq);
|
await adminWalletsHandler.handleBackToWalletTypes(cq);
|
||||||
});
|
});
|
||||||
|
callbackRouter.registerExact('admin_users', async (cq) => {
|
||||||
|
logDebug(cq.data, 'handleUserListBack');
|
||||||
|
await adminUserHandler.handleUserListBack(cq);
|
||||||
|
});
|
||||||
|
|
||||||
// === Prefix Callback Routes ===
|
// === Prefix Callback Routes ===
|
||||||
callbackRouter.registerPrefix('set_country_', async (cq) => {
|
callbackRouter.registerPrefix('set_country_', async (cq) => {
|
||||||
@@ -297,6 +309,10 @@ export function registerRoutes() {
|
|||||||
logDebug(cq.data, 'handleDistrictSelection');
|
logDebug(cq.data, 'handleDistrictSelection');
|
||||||
await productHandler.handleDistrictSelection(cq);
|
await productHandler.handleDistrictSelection(cq);
|
||||||
});
|
});
|
||||||
|
callbackRouter.registerPrefix('prod_district_', async (cq) => {
|
||||||
|
logDebug(cq.data, 'handleDistrictBack');
|
||||||
|
await productHandler.handleDistrictBack(cq);
|
||||||
|
});
|
||||||
callbackRouter.registerPrefix('add_category_', async (cq) => {
|
callbackRouter.registerPrefix('add_category_', async (cq) => {
|
||||||
logDebug(cq.data, 'handleAddCategory');
|
logDebug(cq.data, 'handleAddCategory');
|
||||||
await productHandler.handleAddCategory(cq);
|
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) {
|
static async getLocation(country, city, district) {
|
||||||
try {
|
try {
|
||||||
const location = await db.getAsync(
|
const location = await db.getAsync(
|
||||||
|
|||||||
Reference in New Issue
Block a user