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
|
finalQuantity
|
||||||
);
|
);
|
||||||
|
|
||||||
await bot.AnswerCallbackQueryAsync(
|
// Send new message with post-add prompt
|
||||||
callbackQuery.Id,
|
await bot.SendTextMessageAsync(
|
||||||
$"✅ Added {finalQuantity}x {itemName} to cart",
|
callbackQuery.Message!.Chat.Id,
|
||||||
showAlert: false
|
$"✅ 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)
|
private async Task HandleViewCart(ITelegramBotClient bot, Message message, UserSession session)
|
||||||
@ -1453,18 +1451,11 @@ namespace TeleBot.Handlers
|
|||||||
|
|
||||||
session.TempData["selected_variants"] = selectedVariants;
|
session.TempData["selected_variants"] = selectedVariants;
|
||||||
|
|
||||||
var quantity = session.TempData.ContainsKey("current_quantity")
|
// Update the product detail menu with the selected variants
|
||||||
? (int)session.TempData["current_quantity"]
|
|
||||||
: 1;
|
|
||||||
|
|
||||||
var multiBuyId = session.TempData.ContainsKey("current_multibuy")
|
|
||||||
? session.TempData["current_multibuy"] as string
|
|
||||||
: null;
|
|
||||||
|
|
||||||
await bot.EditMessageReplyMarkupAsync(
|
await bot.EditMessageReplyMarkupAsync(
|
||||||
message.Chat.Id,
|
message.Chat.Id,
|
||||||
message.MessageId,
|
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
|
// Add to cart with selected variants
|
||||||
var cartItem = session.Cart.AddItem(product, quantity.Value, multiBuyId, variantId: null, selectedVariants);
|
var cartItem = session.Cart.AddItem(product, quantity.Value, multiBuyId, variantId: null, selectedVariants);
|
||||||
|
|
||||||
// Show success message
|
// Send new message with post-add prompt
|
||||||
await bot.AnswerCallbackQueryAsync(
|
await bot.SendTextMessageAsync(
|
||||||
callbackQuery.Id,
|
callbackQuery.Message!.Chat.Id,
|
||||||
$"✅ Added {quantity.Value}x {product.Name} with {string.Join(", ", selectedVariants)} to cart!",
|
$"✅ Added {product.Name} ({string.Join(", ", selectedVariants)}) to basket!\n\nWhat would you like to do next?",
|
||||||
showAlert: true
|
replyMarkup: _menuBuilder.PostAddToCartMenu(productId.Value)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Return to product view
|
|
||||||
await HandleProductDetail(bot, callbackQuery.Message!, session, productId.Value);
|
|
||||||
|
|
||||||
// Clear temp data
|
// Clear temp data
|
||||||
session.TempData.Remove("selected_variants");
|
session.TempData.Remove("selected_variants");
|
||||||
session.TempData.Remove("current_multibuy");
|
session.TempData.Remove("current_multibuy");
|
||||||
|
|||||||
@ -21,7 +21,7 @@ namespace TeleBot.UI
|
|||||||
return new InlineKeyboardMarkup(new[]
|
return new InlineKeyboardMarkup(new[]
|
||||||
{
|
{
|
||||||
new[] { InlineKeyboardButton.WithCallbackData("🛍️ Browse Products", "browse") },
|
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("📦 My Orders", "orders") },
|
||||||
new[] { InlineKeyboardButton.WithCallbackData("⭐ Reviews", "reviews") },
|
new[] { InlineKeyboardButton.WithCallbackData("⭐ Reviews", "reviews") },
|
||||||
new[] { InlineKeyboardButton.WithCallbackData("💬 Support", "support") },
|
new[] { InlineKeyboardButton.WithCallbackData("💬 Support", "support") },
|
||||||
@ -91,75 +91,92 @@ namespace TeleBot.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
buttons.Add(new[] { InlineKeyboardButton.WithCallbackData("🛒 View Cart", "cart") });
|
buttons.Add(new[] { InlineKeyboardButton.WithCallbackData("🛒 View Basket", "cart") });
|
||||||
buttons.Add(new[] { InlineKeyboardButton.WithCallbackData("⬅️ Back", "browse") });
|
buttons.Add(new[] { InlineKeyboardButton.WithCallbackData("⬅️ Back", "browse") });
|
||||||
|
|
||||||
return new InlineKeyboardMarkup(buttons);
|
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[]>();
|
var buttons = new List<InlineKeyboardButton[]>();
|
||||||
|
selectedVariants ??= new List<string>();
|
||||||
|
|
||||||
// Check if product has variants
|
// Check if product has variants
|
||||||
bool hasVariants = product.Variants?.Any(v => v.IsActive) == true;
|
bool hasVariants = product.Variants?.Any(v => v.IsActive) == true;
|
||||||
|
|
||||||
// Show multi-buy options if available
|
// If product has variants, show variant selection directly (no intermediate button)
|
||||||
if (product.MultiBuys?.Any(mb => mb.IsActive) == true)
|
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[] {
|
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}";
|
string variantInfo = "";
|
||||||
if (multiBuy.Quantity > 1)
|
|
||||||
label += $" (£{multiBuy.PricePerUnit:F2}/each)";
|
|
||||||
|
|
||||||
// Use short callback data
|
// Show price if different from base product price
|
||||||
var callbackData = hasVariants
|
if (variant.Price.HasValue && variant.Price.Value != product.Price)
|
||||||
? _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)
|
variantInfo = $" - £{variant.Price.Value:F2}";
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add regular single item option
|
var isSelected = selectedVariants.Contains(variant.Name);
|
||||||
var singleCallbackData = hasVariants
|
var buttonText = isSelected
|
||||||
? _mapper.BuildCallback("selectvar", product.Id, 1)
|
? $"✅ {variant.Name}{variantInfo}"
|
||||||
: _mapper.BuildCallback("add", product.Id, 1);
|
: $"{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[] {
|
buttons.Add(new[] {
|
||||||
InlineKeyboardButton.WithCallbackData(
|
InlineKeyboardButton.WithCallbackData(
|
||||||
$"🛒 BUY {product.Name} - £{product.Price:F2}",
|
$"✅ Add to Basket",
|
||||||
singleCallbackData
|
callbackData
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// No multi-buys, show quantity selector
|
// No variants, simple add to basket button
|
||||||
var quantityButtons = new List<InlineKeyboardButton>();
|
var addCallbackData = _mapper.BuildCallback("add", product.Id, 1);
|
||||||
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);
|
|
||||||
|
|
||||||
buttons.Add(new[] {
|
buttons.Add(new[] {
|
||||||
InlineKeyboardButton.WithCallbackData(
|
InlineKeyboardButton.WithCallbackData(
|
||||||
$"🛒 BUY {product.Name} - £{product.Price * quantity:F2}",
|
$"🛒 Add to Basket - £{product.Price:F2}",
|
||||||
addCallbackData
|
addCallbackData
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@ -171,6 +188,24 @@ namespace TeleBot.UI
|
|||||||
return new InlineKeyboardMarkup(buttons);
|
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)
|
public InlineKeyboardMarkup CartMenu(ShoppingCart cart)
|
||||||
{
|
{
|
||||||
var buttons = new List<InlineKeyboardButton[]>();
|
var buttons = new List<InlineKeyboardButton[]>();
|
||||||
@ -193,9 +228,9 @@ namespace TeleBot.UI
|
|||||||
InlineKeyboardButton.WithCallbackData("✅ Proceed to Checkout", "checkout")
|
InlineKeyboardButton.WithCallbackData("✅ Proceed to Checkout", "checkout")
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear cart button
|
// Clear basket button
|
||||||
buttons.Add(new[] {
|
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);
|
: _mapper.BuildCallback("add", product.Id, quantity);
|
||||||
|
|
||||||
buttons.Add(new[] {
|
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)) });
|
buttons.Add(new[] { InlineKeyboardButton.WithCallbackData("⬅️ Back", _mapper.BuildCallback("product", product.Id)) });
|
||||||
return new InlineKeyboardMarkup(buttons);
|
return new InlineKeyboardMarkup(buttons);
|
||||||
@ -522,7 +557,7 @@ namespace TeleBot.UI
|
|||||||
|
|
||||||
buttons.Add(new[] {
|
buttons.Add(new[] {
|
||||||
InlineKeyboardButton.WithCallbackData(
|
InlineKeyboardButton.WithCallbackData(
|
||||||
$"✅ Confirm & Add to Cart",
|
$"✅ Confirm & Add to Basket",
|
||||||
callbackData
|
callbackData
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@ -626,10 +661,10 @@ namespace TeleBot.UI
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// No multi-buys, just show regular add to cart
|
// No multi-buys, just show regular add to basket
|
||||||
buttons.Add(new[]
|
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[] {
|
new[] {
|
||||||
InlineKeyboardButton.WithCallbackData("⬅️ Back to Categories", "browse"),
|
InlineKeyboardButton.WithCallbackData("⬅️ Back to Categories", "browse"),
|
||||||
InlineKeyboardButton.WithCallbackData("🛒 View Cart", "cart")
|
InlineKeyboardButton.WithCallbackData("🛒 View Basket", "cart")
|
||||||
},
|
},
|
||||||
new[] {
|
new[] {
|
||||||
InlineKeyboardButton.WithCallbackData("🏠 Main Menu", "menu")
|
InlineKeyboardButton.WithCallbackData("🏠 Main Menu", "menu")
|
||||||
|
|||||||
@ -136,7 +136,7 @@ namespace TeleBot.UI
|
|||||||
sb.AppendLine($"\n🖼️ _{product.Photos.Count} photo(s) available_");
|
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();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
@ -183,7 +183,7 @@ namespace TeleBot.UI
|
|||||||
sb.AppendLine($"\n📝 *Description:*\n{product.Description}");
|
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();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
@ -191,12 +191,12 @@ namespace TeleBot.UI
|
|||||||
public static string FormatCart(ShoppingCart cart)
|
public static string FormatCart(ShoppingCart cart)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine("🛒 *Shopping Cart*\n");
|
sb.AppendLine("🛒 *Shopping Basket*\n");
|
||||||
|
|
||||||
if (cart.IsEmpty())
|
if (cart.IsEmpty())
|
||||||
{
|
{
|
||||||
sb.AppendLine("Your cart is empty.\n");
|
sb.AppendLine("Your basket is empty.\n");
|
||||||
sb.AppendLine("Browse products to add items to your cart.");
|
sb.AppendLine("Browse products to add items to your basket.");
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -363,13 +363,13 @@ namespace TeleBot.UI
|
|||||||
"/start - Start shopping\n" +
|
"/start - Start shopping\n" +
|
||||||
"/browse - Browse categories\n" +
|
"/browse - Browse categories\n" +
|
||||||
"/products - View all products\n" +
|
"/products - View all products\n" +
|
||||||
"/cart - View shopping cart\n" +
|
"/cart - View shopping basket\n" +
|
||||||
"/orders - View your orders\n" +
|
"/orders - View your orders\n" +
|
||||||
"/review - Review delivered products\n" +
|
"/review - Review delivered products\n" +
|
||||||
"/reviews - View all product reviews\n" +
|
"/reviews - View all product reviews\n" +
|
||||||
"/support - Chat with support\n" +
|
"/support - Chat with support\n" +
|
||||||
"/privacy - Privacy policy\n" +
|
"/privacy - Privacy policy\n" +
|
||||||
"/clear - Clear shopping cart\n" +
|
"/clear - Clear shopping basket\n" +
|
||||||
"/cancel - Cancel current operation\n" +
|
"/cancel - Cancel current operation\n" +
|
||||||
"/delete - Delete all your data\n" +
|
"/delete - Delete all your data\n" +
|
||||||
"/help - Show this help message\n\n";
|
"/help - Show this help message\n\n";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user