Deploy Bot commited on
Commit
5e459fe
·
1 Parent(s): d0715ea

Step4_Localize_ProductBrowsing_And_Checkout_Wizard

Browse files
src/controllers/userController.js CHANGED
@@ -180,7 +180,7 @@ exports.browseCategory = async (ctx, catId, prodIndex, mediaIndex = 0) => {
180
  if (!category) return ctx.reply("Kategoriya topilmadi.");
181
 
182
  const products = await Product.find({ category: category.name });
183
- if (!products || products.length === 0) return ctx.reply("Bu bo'limda mahsulotlar yo'q.");
184
 
185
  // Handle Product Index Bounds
186
  let page = parseInt(prodIndex);
@@ -198,12 +198,11 @@ exports.browseCategory = async (ctx, catId, prodIndex, mediaIndex = 0) => {
198
  }
199
 
200
  const formatPrice = (p) => p.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ");
201
- const stockText = product.quantity > 0 ? `✅ Mavjud: ${product.quantity} dona` : `🚫 Sotuvda yo'q`;
202
 
203
  // Calculate Rating
204
  const reviews = product.reviews || [];
205
  const avgRating = reviews.length > 0 ? (reviews.reduce((a, b) => a + b.rating, 0) / reviews.length).toFixed(1) : "0.0";
206
- const star = "⭐️";
207
 
208
  // Prepare Message Content
209
  // Price formatting with discount
@@ -212,7 +211,7 @@ exports.browseCategory = async (ctx, catId, prodIndex, mediaIndex = 0) => {
212
  priceText = `❌ <s>${formatPrice(product.originalPrice)}</s> | ✅ ${formatPrice(product.price)} so'm (-${product.discountPercent}% 🔥)`;
213
  }
214
 
215
- const caption = `<b>${product.name}</b>\n\n${product.description}\n\nNarxi: ${priceText}\n${stockText}\n⭐️ Reyting: ${avgRating} (${reviews.length} ovoz)\n\n📂 ${category.name} (${page + 1}/${products.length})`;
216
 
217
  // Buttons
218
  // Navigation: prev/next product. Media index resets to 0.
@@ -229,25 +228,24 @@ exports.browseCategory = async (ctx, catId, prodIndex, mediaIndex = 0) => {
229
 
230
  let actionRow = [];
231
  if (product.quantity > 0) {
232
- actionRow.push(Markup.button.callback("🛒 Savatga qo'shish", `add_cart_${product.id}`));
233
  } else {
234
- actionRow.push(Markup.button.callback("🚫 Sotuvda yo'q", `noop`));
235
  }
236
 
237
- let ratingRow = [Markup.button.callback("⭐️ Baholash", `rate_ask_${product.id}`)];
238
 
239
  let buttons = [
240
  navigationRow,
241
  ...(mediaRow.length ? [mediaRow] : []),
242
  actionRow,
243
  ratingRow,
244
- [Markup.button.callback("🔙 Kategoriyalarga", "back_to_cats")]
245
  ];
246
 
247
- // Admin Edit Button... (keeping existing logic if possible, or re-adding it)
248
  const config = require('../config');
249
  if (config.ADMIN_IDS.includes(ctx.from.id.toString())) {
250
- // Find where to inject or push
251
  buttons.push([Markup.button.callback("✏️ Tahrirlash (Admin)", `edit_prod_${product.id}`)]);
252
  }
253
 
 
180
  if (!category) return ctx.reply("Kategoriya topilmadi.");
181
 
182
  const products = await Product.find({ category: category.name });
183
+ if (!products || products.length === 0) return ctx.reply(ctx.i18n.no_cats); // Or specific no_products key
184
 
185
  // Handle Product Index Bounds
186
  let page = parseInt(prodIndex);
 
198
  }
199
 
200
  const formatPrice = (p) => p.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ");
201
+ const stockText = product.quantity > 0 ? `${ctx.i18n.stock_yes}: ${product.quantity}` : ctx.i18n.stock_no;
202
 
203
  // Calculate Rating
204
  const reviews = product.reviews || [];
205
  const avgRating = reviews.length > 0 ? (reviews.reduce((a, b) => a + b.rating, 0) / reviews.length).toFixed(1) : "0.0";
 
206
 
207
  // Prepare Message Content
208
  // Price formatting with discount
 
211
  priceText = `❌ <s>${formatPrice(product.originalPrice)}</s> | ✅ ${formatPrice(product.price)} so'm (-${product.discountPercent}% 🔥)`;
212
  }
213
 
214
+ const caption = `<b>${product.name}</b>\n\n${product.description}\n\n${ctx.i18n.price}: ${priceText}\n${stockText}\n⭐️ ${ctx.i18n.rating}: ${avgRating} (${reviews.length})\n\n📂 ${category.name} (${page + 1}/${products.length})`;
215
 
216
  // Buttons
217
  // Navigation: prev/next product. Media index resets to 0.
 
228
 
229
  let actionRow = [];
230
  if (product.quantity > 0) {
231
+ actionRow.push(Markup.button.callback(ctx.i18n.add_to_cart, `add_cart_${product.id}`));
232
  } else {
233
+ actionRow.push(Markup.button.callback(ctx.i18n.stock_no, `noop`));
234
  }
235
 
236
+ let ratingRow = [Markup.button.callback(ctx.i18n.rate_ask, `rate_ask_${product.id}`)];
237
 
238
  let buttons = [
239
  navigationRow,
240
  ...(mediaRow.length ? [mediaRow] : []),
241
  actionRow,
242
  ratingRow,
243
+ [Markup.button.callback(ctx.i18n.back_to_cats, "back_to_cats")]
244
  ];
245
 
246
+ // Admin Edit Button... (keeping existing logic)
247
  const config = require('../config');
248
  if (config.ADMIN_IDS.includes(ctx.from.id.toString())) {
 
249
  buttons.push([Markup.button.callback("✏️ Tahrirlash (Admin)", `edit_prod_${product.id}`)]);
250
  }
251
 
src/scenes/user/checkout.js CHANGED
@@ -10,28 +10,30 @@ const formatPrice = (price) => {
10
  };
11
 
12
  // Common Keyboard
13
- const navKeyboard = (extras = []) => {
14
- const k = [['⬅️ Ortga', '❌ Bekor qilish']];
15
  if (extras.length) k.unshift(...extras);
16
  return Markup.keyboard(k).resize().oneTime();
17
  };
18
 
19
- const cancelKeyboard = Markup.keyboard([['❌ Bekor qilish']]).resize().oneTime();
20
 
21
  // Navigation Helper
22
  const checkNav = (ctx) => {
 
23
  const text = ctx.message.text;
24
- if (text === '❌ Bekor qilish') {
25
- ctx.scene.leave();
26
- userController.start(ctx, "🛑 Jarayon bekor qilindi.");
27
- return 'STOP';
28
- }
29
- if (text === '🏠 Bosh menyu') {
 
30
  ctx.scene.leave();
31
- userController.start(ctx);
32
  return 'STOP';
33
  }
34
- if (text === '⬅️ Ortga') {
35
  return 'BACK';
36
  }
37
  return null;
@@ -119,11 +121,16 @@ const checkoutScene = new Scenes.WizardScene(
119
  const user = await User.findOne({ id: ctx.from.id });
120
  ctx.wizard.state.user = user;
121
 
122
- let keyboard = [[Markup.button.contactRequest("📱 Raqamni yuborish")], ['❌ Bekor qilish']];
123
- let msg = "📞 Telefon raqamingizni yuboring:";
 
 
 
 
124
  if (user && user.phone) {
125
- keyboard = [[Markup.button.text(user.phone)], ['❌ Bekor qilish']];
126
- msg = "📞 Telefon raqamingizni tasdiqlang yoki yangisini yuboring:";
 
127
  }
128
 
129
  ctx.reply(msg, Markup.keyboard(keyboard).resize().oneTime());
@@ -131,33 +138,30 @@ const checkoutScene = new Scenes.WizardScene(
131
  },
132
  // Step 1: Input Phone -> Show Delivery
133
  (ctx) => {
 
134
  const nav = checkNav(ctx);
135
  if (nav === 'STOP') return;
136
  if (nav === 'BACK') {
137
- // Back to Phone Prompt
138
  return ctx.wizard.selectStep(0) && ctx.wizard.steps[0](ctx);
139
  }
140
 
141
  let phone = '';
142
  if (ctx.message.contact) phone = ctx.message.contact.phone_number;
143
  else if (ctx.message.text) {
144
- // Validate
145
  if (!/^\+?[0-9]{9,15}$/.test(ctx.message.text.replace(/\s/g, ''))) {
146
- // If invalid, ask again? Or just return?
147
- // Let's just standard reply
148
- ctx.reply("⚠️ Noto'g'ri raqam format. Qaytadan yuboring:", navKeyboard());
149
- return; // Stay on Step 1? No, step 1 is handler. We need to stay here.
150
  }
151
  phone = ctx.message.text;
152
  } else {
153
- ctx.reply("⚠️ Raqam yuboring.", navKeyboard()); return;
154
  }
155
  ctx.wizard.state.phone = phone;
156
 
157
- ctx.reply("🚚 Yetkazib berish turini tanlang:", Markup.keyboard([
158
- ["🚕 Yandex Go", "📨 BTS Pochta"],
159
- ["🏃 Olib ketish (Samovivoz)"],
160
- ['⬅️ Ortga', '❌ Bekor qilish']
161
  ]).resize().oneTime());
162
  return ctx.wizard.next();
163
  },
 
10
  };
11
 
12
  // Common Keyboard
13
+ const navKeyboard = (ctx, extras = []) => {
14
+ const k = [[ctx.i18n.btn_back_nav, ctx.i18n.btn_cancel]];
15
  if (extras.length) k.unshift(...extras);
16
  return Markup.keyboard(k).resize().oneTime();
17
  };
18
 
19
+ const cancelKeyboard = (ctx) => Markup.keyboard([[ctx.i18n.btn_cancel]]).resize().oneTime();
20
 
21
  // Navigation Helper
22
  const checkNav = (ctx) => {
23
+ if (!ctx.message || !ctx.message.text) return null;
24
  const text = ctx.message.text;
25
+
26
+ // Check localization or fallback
27
+ // Note: Since we rely on text matching, we must match the CURRENT language text.
28
+ // If the user changed language mid-process it might break, but usually they won't.
29
+ const i18n = ctx.i18n || {};
30
+
31
+ if (text === i18n.btn_cancel || text === '❌ Bekor qilish' || text === '❌ Отмена' || text === '❌ Cancel') {
32
  ctx.scene.leave();
33
+ userController.start(ctx, i18n.cancel_process);
34
  return 'STOP';
35
  }
36
+ if (text === i18n.btn_back_nav || text === '⬅️ Ortga' || text === '⬅️ Назад' || text === '⬅️ Back') {
37
  return 'BACK';
38
  }
39
  return null;
 
121
  const user = await User.findOne({ id: ctx.from.id });
122
  ctx.wizard.state.user = user;
123
 
124
+ // Ensure i18n
125
+ const i18n = ctx.i18n || require('../../locales').uz;
126
+
127
+ let keyboard = [[Markup.button.contactRequest(i18n.phone_button)], [i18n.btn_cancel]];
128
+ let msg = i18n.phone_prompt;
129
+
130
  if (user && user.phone) {
131
+ keyboard = [[Markup.button.text(user.phone)], [i18n.btn_cancel]];
132
+ // We can add a specific key: phone_confirm or just reuse phone_prompt
133
+ msg = i18n.phone_prompt;
134
  }
135
 
136
  ctx.reply(msg, Markup.keyboard(keyboard).resize().oneTime());
 
138
  },
139
  // Step 1: Input Phone -> Show Delivery
140
  (ctx) => {
141
+ const i18n = ctx.i18n;
142
  const nav = checkNav(ctx);
143
  if (nav === 'STOP') return;
144
  if (nav === 'BACK') {
 
145
  return ctx.wizard.selectStep(0) && ctx.wizard.steps[0](ctx);
146
  }
147
 
148
  let phone = '';
149
  if (ctx.message.contact) phone = ctx.message.contact.phone_number;
150
  else if (ctx.message.text) {
 
151
  if (!/^\+?[0-9]{9,15}$/.test(ctx.message.text.replace(/\s/g, ''))) {
152
+ ctx.reply("⚠️ Noto'g'ri raqam format. Qaytadan yuboring:", navKeyboard(ctx, []));
153
+ return;
 
 
154
  }
155
  phone = ctx.message.text;
156
  } else {
157
+ ctx.reply("⚠️ Raqam yuboring.", navKeyboard(ctx, [])); return;
158
  }
159
  ctx.wizard.state.phone = phone;
160
 
161
+ ctx.reply(i18n.delivery_select, Markup.keyboard([
162
+ [i18n.delivery_yandex, i18n.delivery_bts],
163
+ [i18n.delivery_pickup],
164
+ [i18n.btn_back_nav, i18n.btn_cancel]
165
  ]).resize().oneTime());
166
  return ctx.wizard.next();
167
  },