telegram-shop-api / src /scenes /admin /addProduct.js
Deploy Bot
Fix: Crash in Add Product (Markup error)
f9c77d2
const { Scenes, Markup } = require('telegraf');
const Product = require('../../models/Product');
const Category = require('../../models/Category');
const userController = require('../../controllers/userController');
const addProductScene = new Scenes.WizardScene(
'ADD_PRODUCT_SCENE',
// Step 1: Ask Category (First!)
async (ctx) => {
// Ensure localization logic
if (!ctx.i18n) {
const locales = require('../../locales');
const User = require('../../models/User');
const user = await User.findOne({ id: ctx.from.id });
ctx.i18n = locales[user ? user.language : 'uz'];
}
const i18n = ctx.i18n;
// Init State
ctx.wizard.state.product = { media: [] };
const categories = await Category.find();
let buttons = categories.map(c => c.name);
buttons.push(i18n.admin.new_cat || "βž• Yangi Kategoriya");
buttons.push(i18n.btn_cancel || "❌ Bekor qilish");
// Filter out any undefined values to prevent crashes
buttons = buttons.filter(b => b);
ctx.reply(i18n.admin.enter_cat || "πŸ“‚ Kategoriyani tanlang:", Markup.keyboard(buttons, { columns: 2 }).resize());
return ctx.wizard.next();
},
// Step 2: Handle Category -> Ask Name
async (ctx) => {
const i18n = ctx.i18n;
const text = ctx.message?.text;
if (text === i18n.btn_cancel) {
ctx.scene.leave();
userController.start(ctx, i18n.cancel_process);
return;
}
if (text === (i18n.admin.new_cat || "βž• Yangi Kategoriya")) {
ctx.wizard.state.isNewCategory = true;
ctx.reply(i18n.admin.enter_new_cat, Markup.keyboard([[i18n.btn_cancel]]).resize());
return ctx.wizard.selectStep(2); // Go to New Category Handler
}
// Validate Existing Category
// We accept text input as category name too, or check against DB if strict
ctx.wizard.state.product.category = text;
ctx.reply(i18n.admin.add_product_title || "✏️ Mahsulot nomini kiriting:", Markup.keyboard([[i18n.btn_cancel]]).resize());
return ctx.wizard.selectStep(3); // Go to Name Handler
},
// Step 3: Handle New Category (If selected) -> Then Ask Name
async (ctx) => {
const i18n = ctx.i18n;
const text = ctx.message?.text;
if (text === i18n.btn_cancel) {
ctx.scene.leave();
userController.start(ctx, i18n.cancel_process);
return;
}
// Save New Category
try {
await new Category({ id: Date.now().toString(), name: text }).save();
ctx.reply(`${i18n.admin.new_cat}: ${text}`);
ctx.wizard.state.product.category = text;
ctx.reply(i18n.admin.add_product_title || "✏️ Mahsulot nomini kiriting:", Markup.keyboard([[i18n.btn_cancel]]).resize());
return ctx.wizard.next();
} catch (e) {
ctx.reply("Xatolik: " + e.message);
return;
}
},
// Step 4: Handle Name -> Ask Condition
(ctx) => {
const i18n = ctx.i18n;
if (ctx.message.text === i18n.btn_cancel) return ctx.scene.leave();
ctx.wizard.state.product.name = ctx.message.text;
ctx.reply("πŸ†• Mahsulot holatini tanlang:", Markup.keyboard([
["πŸ†• Yangi", "♻️ B/U"],
[i18n.btn_cancel]
]).resize());
return ctx.wizard.next();
},
// Step 5: Handle Condition -> Ask Price
(ctx) => {
const i18n = ctx.i18n;
const text = ctx.message.text;
if (text === i18n.btn_cancel) {
ctx.scene.leave();
userController.start(ctx, i18n.cancel_process);
return;
}
if (text === "πŸ†• Yangi") ctx.wizard.state.product.condition = "new";
else if (text === "♻️ B/U") ctx.wizard.state.product.condition = "used";
else return ctx.reply("Iltimos tugmani tanlang: Yangi yoki B/U");
ctx.reply(i18n.admin.enter_price, Markup.keyboard([[i18n.btn_cancel]]).resize());
return ctx.wizard.next();
},
// Step 6: Handle Price -> Ask Quantity
(ctx) => {
const i18n = ctx.i18n;
if (ctx.message.text === i18n.btn_cancel) return ctx.scene.leave();
if (isNaN(ctx.message.text)) return ctx.reply(i18n.admin.error_num);
ctx.wizard.state.product.price = parseInt(ctx.message.text);
ctx.reply(i18n.admin.enter_qty, Markup.keyboard([[i18n.btn_cancel]]).resize());
return ctx.wizard.next();
},
// Step 7: Handle Quantity -> Ask Description
(ctx) => {
const i18n = ctx.i18n;
if (ctx.message.text === i18n.btn_cancel) return ctx.scene.leave();
if (isNaN(ctx.message.text)) return ctx.reply(i18n.admin.error_num);
ctx.wizard.state.product.quantity = parseInt(ctx.message.text);
ctx.reply(i18n.admin.enter_desc, Markup.keyboard([[i18n.btn_cancel]]).resize());
return ctx.wizard.next();
},
// Step 8: Handle Description -> Ask Media
(ctx) => {
const i18n = ctx.i18n;
if (ctx.message.text === i18n.btn_cancel) return ctx.scene.leave();
ctx.wizard.state.product.description = ctx.message.text;
ctx.reply(i18n.admin.media_prompt, Markup.keyboard([i18n.admin.media_done, i18n.btn_cancel]).resize());
return ctx.wizard.next();
},
// Step 9: Handle Media Loop
async (ctx) => {
const i18n = ctx.i18n;
const msg = ctx.message;
if (msg.text === i18n.btn_cancel) {
ctx.scene.leave();
userController.start(ctx, i18n.cancel_process);
return;
}
if (msg.text === i18n.admin.media_done) {
if (ctx.wizard.state.product.media.length === 0) return ctx.reply(i18n.admin.media_error);
// Save Product
try {
const productData = {
id: Date.now(),
...ctx.wizard.state.product
};
await new Product(productData).save();
ctx.reply(`βœ… <b>Saqlandi!</b>\n\n${productData.name} (${productData.condition === 'new' ? 'πŸ†•' : '♻️'})\nπŸ“‚ ${productData.category}`, { parse_mode: 'HTML' });
userController.start(ctx);
return ctx.scene.leave();
} catch (e) {
console.error(e);
ctx.reply("Error");
return ctx.scene.leave();
}
}
if (msg.photo) {
ctx.wizard.state.product.media.push({ type: 'photo', file_id: msg.photo[msg.photo.length - 1].file_id });
ctx.reply(`Rasm (${ctx.wizard.state.product.media.length}/4)`);
} else if (msg.video) {
ctx.wizard.state.product.media.push({ type: 'video', file_id: msg.video.file_id });
ctx.reply(`Video (${ctx.wizard.state.product.media.length}/4)`);
}
}
);
module.exports = addProductScene;