UX: Major TeleBot checkout flow improvements

1. Show variants immediately on product page (removed duplicate buy button)
   - Variants now display directly on product details page
   - No intermediate "select variant" button needed
   - Faster checkout flow

2. Add post-purchase prompt (Checkout or Continue Shopping)
   - After adding to basket, shows "What would you like to do next?"
   - Options: View Basket, Checkout, or Continue Shopping
   - Continue Shopping returns to the product page

3. Remove quantity +/- buttons from product details
   - Simplified to single item purchase only
   - Cleaner interface for mobile users

4. Rename "Cart" to "Basket" throughout
   - All user-facing text updated
   - "Add to Cart" → "Add to Basket"
   - "Shopping Cart" → "Shopping Basket"
   - "View Cart" → "View Basket"
   - "Clear Cart" → "Clear Basket"

Technical Changes:
- MenuBuilder.ProductDetailMenu: Now shows variant selection inline
- CallbackHandler: Updated to use ProductDetailMenu for variant updates
- Added PostAddToCartMenu for post-purchase options
- HandleAddToCart/HandleConfirmVariant: Show prompt instead of returning to product

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
SysAdmin 2025-10-06 03:26:19 +01:00
parent 1e93008df4
commit 9432ae8ccb
3 changed files with 109 additions and 86 deletions

View File

@ -494,14 +494,12 @@ namespace TeleBot.Handlers
finalQuantity
);
await bot.AnswerCallbackQueryAsync(
callbackQuery.Id,
$"✅ Added {finalQuantity}x {itemName} to cart",
showAlert: false
// Send new message with post-add prompt
await bot.SendTextMessageAsync(
callbackQuery.Message!.Chat.Id,
$"✅ Added {product.Name} to basket!\n\nWhat would you like to do next?",
replyMarkup: _menuBuilder.PostAddToCartMenu(productId.Value)
);
// Send new cart message instead of editing
await SendNewCartMessage(bot, callbackQuery.Message!.Chat.Id, session);
}
private async Task HandleViewCart(ITelegramBotClient bot, Message message, UserSession session)
@ -1453,18 +1451,11 @@ namespace TeleBot.Handlers
session.TempData["selected_variants"] = selectedVariants;
var quantity = session.TempData.ContainsKey("current_quantity")
? (int)session.TempData["current_quantity"]
: 1;
var multiBuyId = session.TempData.ContainsKey("current_multibuy")
? session.TempData["current_multibuy"] as string
: null;
// Update the product detail menu with the selected variants
await bot.EditMessageReplyMarkupAsync(
message.Chat.Id,
message.MessageId,
replyMarkup: _menuBuilder.VariantSelectionMenu(product, quantity, multiBuyId, selectedVariants)
replyMarkup: _menuBuilder.ProductDetailMenu(product, 1, selectedVariants)
);
}
@ -1523,16 +1514,13 @@ namespace TeleBot.Handlers
// Add to cart with selected variants
var cartItem = session.Cart.AddItem(product, quantity.Value, multiBuyId, variantId: null, selectedVariants);
// Show success message
await bot.AnswerCallbackQueryAsync(
callbackQuery.Id,
$"✅ Added {quantity.Value}x {product.Name} with {string.Join(", ", selectedVariants)} to cart!",
showAlert: true
// Send new message with post-add prompt
await bot.SendTextMessageAsync(
callbackQuery.Message!.Chat.Id,
$"✅ Added {product.Name} ({string.Join(", ", selectedVariants)}) to basket!\n\nWhat would you like to do next?",
replyMarkup: _menuBuilder.PostAddToCartMenu(productId.Value)
);
// Return to product view
await HandleProductDetail(bot, callbackQuery.Message!, session, productId.Value);
// Clear temp data
session.TempData.Remove("selected_variants");
session.TempData.Remove("current_multibuy");

View File

@ -21,7 +21,7 @@ namespace TeleBot.UI
return new InlineKeyboardMarkup(new[]
{
new[] { InlineKeyboardButton.WithCallbackData("🛍️ Browse Products", "browse") },
new[] { InlineKeyboardButton.WithCallbackData("🛒 View Cart", "cart") },
new[] { InlineKeyboardButton.WithCallbackData("🛒 View Basket", "cart") },
new[] { InlineKeyboardButton.WithCallbackData("📦 My Orders", "orders") },
new[] { InlineKeyboardButton.WithCallbackData("⭐ Reviews", "reviews") },
new[] { InlineKeyboardButton.WithCallbackData("💬 Support", "support") },
@ -91,75 +91,92 @@ namespace TeleBot.UI
}
// Navigation
buttons.Add(new[] { InlineKeyboardButton.WithCallbackData("🛒 View Cart", "cart") });
buttons.Add(new[] { InlineKeyboardButton.WithCallbackData("🛒 View Basket", "cart") });
buttons.Add(new[] { InlineKeyboardButton.WithCallbackData("⬅️ Back", "browse") });
return new InlineKeyboardMarkup(buttons);
}
public InlineKeyboardMarkup ProductDetailMenu(Product product, int quantity = 1)
public InlineKeyboardMarkup ProductDetailMenu(Product product, int quantity = 1, List<string>? selectedVariants = null)
{
var buttons = new List<InlineKeyboardButton[]>();
selectedVariants ??= new List<string>();
// Check if product has variants
bool hasVariants = product.Variants?.Any(v => v.IsActive) == true;
// Show multi-buy options if available
if (product.MultiBuys?.Any(mb => mb.IsActive) == true)
// If product has variants, show variant selection directly (no intermediate button)
if (hasVariants)
{
// Group variants by type
var variantGroups = product.Variants!
.Where(v => v.IsActive)
.GroupBy(v => v.VariantType)
.ToList();
// For each variant type, show options
foreach (var group in variantGroups)
{
buttons.Add(new[] {
InlineKeyboardButton.WithCallbackData("💰 Multi-Buy Deals:", "noop")
InlineKeyboardButton.WithCallbackData($"Select {group.Key}:", "noop")
});
foreach (var multiBuy in product.MultiBuys.Where(mb => mb.IsActive).OrderBy(mb => mb.Quantity))
var variantButtons = new List<InlineKeyboardButton>();
foreach (var variant in group.OrderBy(v => v.SortOrder))
{
var label = $"{multiBuy.Name} - £{multiBuy.Price:F2}";
if (multiBuy.Quantity > 1)
label += $" (£{multiBuy.PricePerUnit:F2}/each)";
string variantInfo = "";
// Use short callback data
var callbackData = hasVariants
? _mapper.BuildCallback("selectvar", product.Id, multiBuy.Quantity, multiBuy.Id)
: _mapper.BuildCallback("add", product.Id, multiBuy.Quantity, multiBuy.Id);
buttons.Add(new[]
// Show price if different from base product price
if (variant.Price.HasValue && variant.Price.Value != product.Price)
{
InlineKeyboardButton.WithCallbackData(label, callbackData)
});
variantInfo = $" - £{variant.Price.Value:F2}";
}
// Add regular single item option
var singleCallbackData = hasVariants
? _mapper.BuildCallback("selectvar", product.Id, 1)
: _mapper.BuildCallback("add", product.Id, 1);
var isSelected = selectedVariants.Contains(variant.Name);
var buttonText = isSelected
? $"✅ {variant.Name}{variantInfo}"
: $"{variant.Name}{variantInfo}";
var callbackData = $"setvariant:{_mapper.GetShortId(product.Id, "p")}:{variant.Name}";
variantButtons.Add(InlineKeyboardButton.WithCallbackData(
buttonText,
callbackData
));
}
// Add variant buttons in rows of 3
for (int i = 0; i < variantButtons.Count; i += 3)
{
buttons.Add(variantButtons.Skip(i).Take(3).ToArray());
}
}
// Check if all variant types have been selected
bool canConfirm = variantGroups.All(g => g.Any(v => selectedVariants.Contains(v.Name)));
if (canConfirm)
{
var variantString = string.Join(",", selectedVariants);
var shortProductId = _mapper.GetShortId(product.Id, "p");
var callbackData = $"confirmvar:{shortProductId}:1:null:{variantString}";
buttons.Add(new[] {
InlineKeyboardButton.WithCallbackData(
$"🛒 BUY {product.Name} - £{product.Price:F2}",
singleCallbackData
$"✅ Add to Basket",
callbackData
)
});
}
}
else
{
// No multi-buys, show quantity selector
var quantityButtons = new List<InlineKeyboardButton>();
if (quantity > 1)
quantityButtons.Add(InlineKeyboardButton.WithCallbackData("", _mapper.BuildCallback("qty", product.Id, quantity - 1)));
quantityButtons.Add(InlineKeyboardButton.WithCallbackData($"Qty: {quantity}", "noop"));
if (quantity < 10)
quantityButtons.Add(InlineKeyboardButton.WithCallbackData("", _mapper.BuildCallback("qty", product.Id, quantity + 1)));
buttons.Add(quantityButtons.ToArray());
// Buy button
var addCallbackData = hasVariants
? _mapper.BuildCallback("selectvar", product.Id, quantity)
: _mapper.BuildCallback("add", product.Id, quantity);
// No variants, simple add to basket button
var addCallbackData = _mapper.BuildCallback("add", product.Id, 1);
buttons.Add(new[] {
InlineKeyboardButton.WithCallbackData(
$"🛒 BUY {product.Name} - £{product.Price * quantity:F2}",
$"🛒 Add to Basket - £{product.Price:F2}",
addCallbackData
)
});
@ -171,6 +188,24 @@ namespace TeleBot.UI
return new InlineKeyboardMarkup(buttons);
}
public InlineKeyboardMarkup PostAddToCartMenu(Guid productId)
{
var buttons = new List<InlineKeyboardButton[]>
{
new[]
{
InlineKeyboardButton.WithCallbackData("🛒 View Basket", "cart"),
InlineKeyboardButton.WithCallbackData("💳 Checkout", "checkout")
},
new[]
{
InlineKeyboardButton.WithCallbackData("◀️ Continue Shopping", _mapper.BuildCallback("product", productId))
}
};
return new InlineKeyboardMarkup(buttons);
}
public InlineKeyboardMarkup CartMenu(ShoppingCart cart)
{
var buttons = new List<InlineKeyboardButton[]>();
@ -193,9 +228,9 @@ namespace TeleBot.UI
InlineKeyboardButton.WithCallbackData("✅ Proceed to Checkout", "checkout")
});
// Clear cart button
// Clear basket button
buttons.Add(new[] {
InlineKeyboardButton.WithCallbackData("🗑️ Clear Cart", "clear_cart")
InlineKeyboardButton.WithCallbackData("🗑️ Clear Basket", "clear_cart")
});
}
@ -379,7 +414,7 @@ namespace TeleBot.UI
: _mapper.BuildCallback("add", product.Id, quantity);
buttons.Add(new[] {
InlineKeyboardButton.WithCallbackData($"🛒 Add to Cart", callbackData)
InlineKeyboardButton.WithCallbackData($"🛒 Add to Basket", callbackData)
});
buttons.Add(new[] { InlineKeyboardButton.WithCallbackData("⬅️ Back", _mapper.BuildCallback("product", product.Id)) });
return new InlineKeyboardMarkup(buttons);
@ -522,7 +557,7 @@ namespace TeleBot.UI
buttons.Add(new[] {
InlineKeyboardButton.WithCallbackData(
$"✅ Confirm & Add to Cart",
$"✅ Confirm & Add to Basket",
callbackData
)
});
@ -626,10 +661,10 @@ namespace TeleBot.UI
}
else
{
// No multi-buys, just show regular add to cart
// No multi-buys, just show regular add to basket
buttons.Add(new[]
{
InlineKeyboardButton.WithCallbackData($"Add to Cart - £{product.Price:F2}", $"add:{product.Id}:{defaultQuantity}")
InlineKeyboardButton.WithCallbackData($"Add to Basket - £{product.Price:F2}", $"add:{product.Id}:{defaultQuantity}")
});
}
@ -669,7 +704,7 @@ namespace TeleBot.UI
{
new[] {
InlineKeyboardButton.WithCallbackData("⬅️ Back to Categories", "browse"),
InlineKeyboardButton.WithCallbackData("🛒 View Cart", "cart")
InlineKeyboardButton.WithCallbackData("🛒 View Basket", "cart")
},
new[] {
InlineKeyboardButton.WithCallbackData("🏠 Main Menu", "menu")

View File

@ -136,7 +136,7 @@ namespace TeleBot.UI
sb.AppendLine($"\n🖼 _{product.Photos.Count} photo(s) available_");
}
sb.AppendLine("\nSelect quantity and add to cart:");
sb.AppendLine("\nSelect quantity and add to basket:");
return sb.ToString();
}
@ -183,7 +183,7 @@ namespace TeleBot.UI
sb.AppendLine($"\n📝 *Description:*\n{product.Description}");
}
sb.AppendLine("\n*Select an option to add to cart:*");
sb.AppendLine("\n*Select an option to add to basket:*");
return sb.ToString();
}
@ -191,12 +191,12 @@ namespace TeleBot.UI
public static string FormatCart(ShoppingCart cart)
{
var sb = new StringBuilder();
sb.AppendLine("🛒 *Shopping Cart*\n");
sb.AppendLine("🛒 *Shopping Basket*\n");
if (cart.IsEmpty())
{
sb.AppendLine("Your cart is empty.\n");
sb.AppendLine("Browse products to add items to your cart.");
sb.AppendLine("Your basket is empty.\n");
sb.AppendLine("Browse products to add items to your basket.");
return sb.ToString();
}
@ -363,13 +363,13 @@ namespace TeleBot.UI
"/start - Start shopping\n" +
"/browse - Browse categories\n" +
"/products - View all products\n" +
"/cart - View shopping cart\n" +
"/cart - View shopping basket\n" +
"/orders - View your orders\n" +
"/review - Review delivered products\n" +
"/reviews - View all product reviews\n" +
"/support - Chat with support\n" +
"/privacy - Privacy policy\n" +
"/clear - Clear shopping cart\n" +
"/clear - Clear shopping basket\n" +
"/cancel - Cancel current operation\n" +
"/delete - Delete all your data\n" +
"/help - Show this help message\n\n";