fix(bot): issue #129 — missing shop_district_/shop_subcategory_ handlers, answerCallbackQuery timing, state.location pipe format
- Register shop_district_ (handleDistrictBack) + shop_subcategory_ (handleSubcategorySelection) in routes.js — Back button in empty categories now works - state.location pipe-delimited with encodeURIComponent — multi-word names (Saint Petersburg) no longer break nav - answerCallbackQuery moved to start of callback handling — no stuck spinner - Empty city fallback (district_unknown) in Select district - Guard against stale underscore-format state.location - 48 tests pass; bump v1.2.4
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.3</span>
|
||||
<span class="fs-xs opacity-70">v1.2.4</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.3 · 2026-08-04
|
||||
<strong>Current:</strong> v1.2.4 · 2026-08-05
|
||||
</div>
|
||||
|
||||
<h6 class="fw-bold mb-2">v1.2.3 <span class="text-muted fs-sm">— 2026-08-04</span></h6>
|
||||
<h6 class="fw-bold mb-2">v1.2.4 <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>
|
||||
|
||||
@@ -159,7 +159,7 @@ export default class UserProductHandler {
|
||||
};
|
||||
|
||||
await bot.editMessageText(
|
||||
t('products.select_district', { city }),
|
||||
t('products.select_district', { city: city || t('products.district_unknown') }),
|
||||
{
|
||||
chat_id: chatId,
|
||||
message_id: messageId,
|
||||
@@ -208,7 +208,7 @@ export default class UserProductHandler {
|
||||
}
|
||||
|
||||
await userStates.set(chatId, {
|
||||
location: `${location.country}_${location.city}_${location.district}`,
|
||||
location: `${encodeURIComponent(location.country)}|${encodeURIComponent(location.city)}|${encodeURIComponent(location.district)}`,
|
||||
locationId: location.id
|
||||
});
|
||||
|
||||
@@ -259,6 +259,56 @@ export default class UserProductHandler {
|
||||
}
|
||||
}
|
||||
|
||||
static async handleDistrictBack(callbackQuery) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const messageId = callbackQuery.message.message_id;
|
||||
|
||||
try {
|
||||
const telegramId = callbackQuery.from.id;
|
||||
const user = await UserService.getUserByTelegramId(telegramId);
|
||||
const lang = user?.language || 'en';
|
||||
const t = tForUser(lang);
|
||||
|
||||
const state = await userStates.get(chatId);
|
||||
if (!state?.location || !state.location.includes('|')) {
|
||||
await bot.editMessageText(t('products.error_loading_districts'), {
|
||||
chat_id: chatId,
|
||||
message_id: messageId
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const [country, city] = state.location.split('|').map(decodeURIComponent);
|
||||
const locations = await LocationService.getLocationsByCountryAndCity(country, city);
|
||||
|
||||
const keyboard = {
|
||||
inline_keyboard: [
|
||||
...locations.map(loc => [{
|
||||
text: loc.district || loc.city,
|
||||
callback_data: `shop_loc_${loc.id}`
|
||||
}]),
|
||||
[{ text: t('products.back_to_cities'), callback_data: `shop_country_${encodeURIComponent(country)}` }]
|
||||
]
|
||||
};
|
||||
|
||||
await bot.editMessageText(
|
||||
t('products.select_district', { city: city || t('products.district_unknown') }),
|
||||
{
|
||||
chat_id: chatId,
|
||||
message_id: messageId,
|
||||
reply_markup: keyboard
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error in handleDistrictBack');
|
||||
const telegramId = callbackQuery.from.id;
|
||||
const user = await UserService.getUserByTelegramId(telegramId).catch(() => null);
|
||||
const lang = user?.language || 'en';
|
||||
const t = tForUser(lang);
|
||||
await bot.sendMessage(chatId, t('products.error_loading_districts'));
|
||||
}
|
||||
}
|
||||
|
||||
static async handleCategorySelection(callbackQuery) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const messageId = callbackQuery.message.message_id;
|
||||
|
||||
@@ -60,7 +60,8 @@
|
||||
"mono_product": "📦 Digitales Produkt",
|
||||
"infinite_stock": "∞ Immer verfügbar",
|
||||
"no_description": "Keine Beschreibung",
|
||||
"no_photo": "Kein Foto verfügbar"
|
||||
"no_photo": "Kein Foto verfügbar",
|
||||
"district_unknown": "Unbekannt"
|
||||
},
|
||||
"purchase": {
|
||||
"summary": "🛒 Kaufübersicht:",
|
||||
|
||||
@@ -60,7 +60,8 @@
|
||||
"mono_product": "📦 Digital Product",
|
||||
"infinite_stock": "∞ Always available",
|
||||
"no_description": "No description provided",
|
||||
"no_photo": "No photo available"
|
||||
"no_photo": "No photo available",
|
||||
"district_unknown": "Unknown"
|
||||
},
|
||||
"purchase": {
|
||||
"summary": "🛒 Purchase Summary:",
|
||||
|
||||
@@ -60,7 +60,8 @@
|
||||
"mono_product": "📦 Producto Digital",
|
||||
"infinite_stock": "∞ Siempre disponible",
|
||||
"no_description": "Sin descripción",
|
||||
"no_photo": "Sin foto disponible"
|
||||
"no_photo": "Sin foto disponible",
|
||||
"district_unknown": "Desconocido"
|
||||
},
|
||||
"purchase": {
|
||||
"summary": "🛒 Resumen de compra:",
|
||||
|
||||
@@ -72,15 +72,10 @@ if (bot && botAvailable) {
|
||||
await bot.answerCallbackQuery(callbackQuery.id, { text: '⏳' }).catch(() => {});
|
||||
return;
|
||||
}
|
||||
let answered = false;
|
||||
await bot.answerCallbackQuery(callbackQuery.id).catch(() => {});
|
||||
try {
|
||||
await callbackRouter.dispatch(callbackQuery);
|
||||
await bot.answerCallbackQuery(callbackQuery.id);
|
||||
answered = true;
|
||||
} catch (error) {
|
||||
if (!answered) {
|
||||
await bot.answerCallbackQuery(callbackQuery.id).catch(() => {});
|
||||
}
|
||||
await ErrorHandler.handleError(bot, callbackQuery.message.chat.id, error, 'callback query');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -237,10 +237,18 @@ export function registerRoutes() {
|
||||
logDebug(cq.data, 'handleDistrictSelection');
|
||||
await userProductHandler.handleDistrictSelection(cq);
|
||||
});
|
||||
callbackRouter.registerPrefix('shop_district_', async (cq) => {
|
||||
logDebug(cq.data, 'handleDistrictBack');
|
||||
await userProductHandler.handleDistrictBack(cq);
|
||||
});
|
||||
callbackRouter.registerPrefix('shop_category_', async (cq) => {
|
||||
logDebug(cq.data, 'handleCategorySelection');
|
||||
await userProductHandler.handleCategorySelection(cq);
|
||||
});
|
||||
callbackRouter.registerPrefix('shop_subcategory_', async (cq) => {
|
||||
logDebug(cq.data, 'handleSubcategorySelection');
|
||||
await userProductHandler.handleSubcategorySelection(cq);
|
||||
});
|
||||
callbackRouter.registerPrefix('shop_product_', async (cq) => {
|
||||
logDebug(cq.data, 'handleProductSelection');
|
||||
await userProductHandler.handleProductSelection(cq);
|
||||
|
||||
Reference in New Issue
Block a user