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:
parent
1e93008df4
commit
9432ae8ccb
@ -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");
|
||||
|
||||
@ -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)
|
||||
{
|
||||
buttons.Add(new[] {
|
||||
InlineKeyboardButton.WithCallbackData("💰 Multi-Buy Deals:", "noop")
|
||||
});
|
||||
// Group variants by type
|
||||
var variantGroups = product.Variants!
|
||||
.Where(v => v.IsActive)
|
||||
.GroupBy(v => v.VariantType)
|
||||
.ToList();
|
||||
|
||||
foreach (var multiBuy in product.MultiBuys.Where(mb => mb.IsActive).OrderBy(mb => mb.Quantity))
|
||||
// For each variant type, show options
|
||||
foreach (var group in variantGroups)
|
||||
{
|
||||
var label = $"{multiBuy.Name} - £{multiBuy.Price:F2}";
|
||||
if (multiBuy.Quantity > 1)
|
||||
label += $" (£{multiBuy.PricePerUnit:F2}/each)";
|
||||
|
||||
// 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[]
|
||||
{
|
||||
InlineKeyboardButton.WithCallbackData(label, callbackData)
|
||||
buttons.Add(new[] {
|
||||
InlineKeyboardButton.WithCallbackData($"Select {group.Key}:", "noop")
|
||||
});
|
||||
|
||||
var variantButtons = new List<InlineKeyboardButton>();
|
||||
foreach (var variant in group.OrderBy(v => v.SortOrder))
|
||||
{
|
||||
string variantInfo = "";
|
||||
|
||||
// Show price if different from base product price
|
||||
if (variant.Price.HasValue && variant.Price.Value != product.Price)
|
||||
{
|
||||
variantInfo = $" - £{variant.Price.Value:F2}";
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// Add regular single item option
|
||||
var singleCallbackData = hasVariants
|
||||
? _mapper.BuildCallback("selectvar", product.Id, 1)
|
||||
: _mapper.BuildCallback("add", product.Id, 1);
|
||||
// Check if all variant types have been selected
|
||||
bool canConfirm = variantGroups.All(g => g.Any(v => selectedVariants.Contains(v.Name)));
|
||||
|
||||
buttons.Add(new[] {
|
||||
InlineKeyboardButton.WithCallbackData(
|
||||
$"🛒 BUY {product.Name} - £{product.Price:F2}",
|
||||
singleCallbackData
|
||||
)
|
||||
});
|
||||
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(
|
||||
$"✅ 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")
|
||||
|
||||
@ -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";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user