Implement product variant selection in TeleBot
FEATURES IMPLEMENTED: 1. Enhanced Product Display: - Shows multi-buy deals with pricing (e.g., "3 for £25") - Displays available variants grouped by type (Color, Flavor, etc.) - Clear visual separation between multi-buys and variants 2. Variant Selection Flow: - Single item: Select one variant from available options - Multi-buy bundles: Select individual variants for each item - Example: 3-pack allows choosing Red, Blue, Green individually - Visual feedback with checkmarks and counters 3. Smart Cart Management: - Tracks selected variants for each cart item - Supports both single variant (regular items) and multiple variants (multi-buys) - Unique cart entries based on product + variant combination - Prevents duplicate multi-buy bundles 4. User Experience Improvements: - Clear "Select Color/Flavor" prompts - Progress indicator for multi-item selection - Confirm button appears when selection complete - Clear selection option for multi-buys - Back navigation preserves context TECHNICAL CHANGES: - ProductCarouselService: Enhanced caption formatting with variants/multi-buys - MenuBuilder: New VariantSelectionMenu with dynamic button generation - CallbackHandler: Added handlers for selectvar, setvariant, addvariant, confirmvar - ShoppingCart: New AddItem overload accepting Product and variant list - CartItem: Added SelectedVariants list for multi-buy support - UserSession: Added SelectingVariants state This update enables customers to: - See all available product options at a glance - Choose specific variants when ordering - Mix and match variants in multi-buy deals - Get exactly what they want with clear visual feedback Next steps: Add bot activity tracking for live dashboard 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
5530f9e4f5
commit
f12f35cc48
@ -171,6 +171,22 @@ namespace TeleBot.Handlers
|
|||||||
await HandleCancelSupport(bot, callbackQuery, session);
|
await HandleCancelSupport(bot, callbackQuery, session);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "selectvar":
|
||||||
|
await HandleSelectVariant(bot, callbackQuery.Message, session, data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "setvariant":
|
||||||
|
await HandleSetVariant(bot, callbackQuery.Message, session, data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "addvariant":
|
||||||
|
await HandleAddVariant(bot, callbackQuery.Message, session, data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "confirmvar":
|
||||||
|
await HandleConfirmVariant(bot, callbackQuery, session, data);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
_logger.LogWarning("Unknown callback action: {Action}", action);
|
_logger.LogWarning("Unknown callback action: {Action}", action);
|
||||||
break;
|
break;
|
||||||
@ -1126,5 +1142,122 @@ namespace TeleBot.Handlers
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task HandleSelectVariant(ITelegramBotClient bot, Message message, UserSession session, string[] data)
|
||||||
|
{
|
||||||
|
// Format: selectvar:productId:quantity:multiBuyId
|
||||||
|
var productId = Guid.Parse(data[1]);
|
||||||
|
var quantity = int.Parse(data[2]);
|
||||||
|
var multiBuyId = data.Length > 3 && data[3] != "null" ? data[3] : null;
|
||||||
|
|
||||||
|
var product = await _shopService.GetProductAsync(productId);
|
||||||
|
if (product == null)
|
||||||
|
{
|
||||||
|
await bot.SendTextMessageAsync(message.Chat.Id, "Product not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear any previous selections
|
||||||
|
session.TempData["selected_variants"] = new List<string>();
|
||||||
|
session.TempData["current_product"] = productId;
|
||||||
|
session.TempData["current_quantity"] = quantity;
|
||||||
|
session.TempData["current_multibuy"] = multiBuyId;
|
||||||
|
|
||||||
|
await bot.EditMessageReplyMarkupAsync(
|
||||||
|
message.Chat.Id,
|
||||||
|
message.MessageId,
|
||||||
|
replyMarkup: MenuBuilder.VariantSelectionMenu(product, quantity, multiBuyId)
|
||||||
|
);
|
||||||
|
|
||||||
|
session.State = SessionState.SelectingVariants;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleSetVariant(ITelegramBotClient bot, Message message, UserSession session, string[] data)
|
||||||
|
{
|
||||||
|
// Format: setvariant:productId:variantName (for single item)
|
||||||
|
var productId = Guid.Parse(data[1]);
|
||||||
|
var variantName = data[2];
|
||||||
|
|
||||||
|
var product = await _shopService.GetProductAsync(productId);
|
||||||
|
if (product == null) return;
|
||||||
|
|
||||||
|
// For single item, replace selection
|
||||||
|
var selectedVariants = new List<string> { variantName };
|
||||||
|
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;
|
||||||
|
|
||||||
|
await bot.EditMessageReplyMarkupAsync(
|
||||||
|
message.Chat.Id,
|
||||||
|
message.MessageId,
|
||||||
|
replyMarkup: MenuBuilder.VariantSelectionMenu(product, quantity, multiBuyId, selectedVariants)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleAddVariant(ITelegramBotClient bot, Message message, UserSession session, string[] data)
|
||||||
|
{
|
||||||
|
// Format: addvariant:productId:quantity:variantName:multiBuyId (for multi-buy)
|
||||||
|
var productId = Guid.Parse(data[1]);
|
||||||
|
var quantity = int.Parse(data[2]);
|
||||||
|
var variantName = data[3];
|
||||||
|
var multiBuyId = data.Length > 4 && data[4] != "null" ? data[4] : null;
|
||||||
|
|
||||||
|
var product = await _shopService.GetProductAsync(productId);
|
||||||
|
if (product == null) return;
|
||||||
|
|
||||||
|
// Get current selections
|
||||||
|
var selectedVariants = session.TempData.ContainsKey("selected_variants")
|
||||||
|
? (List<string>)session.TempData["selected_variants"]
|
||||||
|
: new List<string>();
|
||||||
|
|
||||||
|
// Add variant if not at quantity limit
|
||||||
|
if (selectedVariants.Count < quantity)
|
||||||
|
{
|
||||||
|
selectedVariants.Add(variantName);
|
||||||
|
session.TempData["selected_variants"] = selectedVariants;
|
||||||
|
}
|
||||||
|
|
||||||
|
await bot.EditMessageReplyMarkupAsync(
|
||||||
|
message.Chat.Id,
|
||||||
|
message.MessageId,
|
||||||
|
replyMarkup: MenuBuilder.VariantSelectionMenu(product, quantity, multiBuyId, selectedVariants)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleConfirmVariant(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session, string[] data)
|
||||||
|
{
|
||||||
|
// Format: confirmvar:productId:quantity:multiBuyId:variantList
|
||||||
|
var productId = Guid.Parse(data[1]);
|
||||||
|
var quantity = int.Parse(data[2]);
|
||||||
|
var multiBuyId = data[3] != "null" ? Guid.Parse(data[3]) : (Guid?)null;
|
||||||
|
var variantString = data[4];
|
||||||
|
var selectedVariants = variantString.Split(',').ToList();
|
||||||
|
|
||||||
|
var product = await _shopService.GetProductAsync(productId);
|
||||||
|
if (product == null) return;
|
||||||
|
|
||||||
|
// Add to cart with selected variants
|
||||||
|
var cartItem = session.Cart.AddItem(product, quantity, multiBuyId, selectedVariants);
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
await bot.AnswerCallbackQueryAsync(
|
||||||
|
callbackQuery.Id,
|
||||||
|
$"✅ Added {quantity}x {product.Name} with {string.Join(", ", selectedVariants)} to cart!",
|
||||||
|
showAlert: true
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return to product view
|
||||||
|
await HandleProductDetail(bot, callbackQuery.Message!, session, productId);
|
||||||
|
|
||||||
|
// Clear temp data
|
||||||
|
session.TempData.Remove("selected_variants");
|
||||||
|
session.TempData.Remove("current_multibuy");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using LittleShop.Client.Models;
|
||||||
|
|
||||||
namespace TeleBot.Models
|
namespace TeleBot.Models
|
||||||
{
|
{
|
||||||
@ -11,6 +12,64 @@ namespace TeleBot.Models
|
|||||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
// New method that accepts Product and variants list
|
||||||
|
public CartItem AddItem(Product product, int quantity = 1, Guid? multiBuyId = null, List<string>? selectedVariants = null)
|
||||||
|
{
|
||||||
|
decimal price = product.Price;
|
||||||
|
|
||||||
|
// If multi-buy selected, get the multi-buy price
|
||||||
|
if (multiBuyId.HasValue && product.MultiBuys != null)
|
||||||
|
{
|
||||||
|
var multiBuy = product.MultiBuys.FirstOrDefault(mb => mb.Id == multiBuyId.Value);
|
||||||
|
if (multiBuy != null)
|
||||||
|
{
|
||||||
|
price = multiBuy.Price;
|
||||||
|
quantity = multiBuy.Quantity; // Use the multi-buy quantity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create unique key for comparison
|
||||||
|
var variantKey = selectedVariants != null && selectedVariants.Any()
|
||||||
|
? string.Join(",", selectedVariants.OrderBy(v => v))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// Check if item already exists
|
||||||
|
var existingItem = Items.FirstOrDefault(i =>
|
||||||
|
i.ProductId == product.Id &&
|
||||||
|
i.MultiBuyId == multiBuyId &&
|
||||||
|
(i.SelectedVariant == variantKey ||
|
||||||
|
(i.SelectedVariants != null && string.Join(",", i.SelectedVariants.OrderBy(v => v)) == variantKey)));
|
||||||
|
|
||||||
|
if (existingItem != null)
|
||||||
|
{
|
||||||
|
// For multi-buys, we don't add quantities - each multi-buy is a separate bundle
|
||||||
|
if (!multiBuyId.HasValue)
|
||||||
|
{
|
||||||
|
existingItem.Quantity += quantity;
|
||||||
|
existingItem.UpdateTotalPrice();
|
||||||
|
}
|
||||||
|
return existingItem;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var newItem = new CartItem
|
||||||
|
{
|
||||||
|
ProductId = product.Id,
|
||||||
|
MultiBuyId = multiBuyId,
|
||||||
|
SelectedVariant = selectedVariants?.Count == 1 ? selectedVariants[0] : null,
|
||||||
|
SelectedVariants = selectedVariants ?? new List<string>(),
|
||||||
|
ProductName = product.Name,
|
||||||
|
UnitPrice = price,
|
||||||
|
Quantity = quantity
|
||||||
|
};
|
||||||
|
newItem.UpdateTotalPrice();
|
||||||
|
Items.Add(newItem);
|
||||||
|
UpdatedAt = DateTime.UtcNow;
|
||||||
|
return newItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the old method for backward compatibility
|
||||||
public void AddItem(Guid productId, string productName, decimal price, int quantity = 1, Guid? multiBuyId = null, string? selectedVariant = null)
|
public void AddItem(Guid productId, string productName, decimal price, int quantity = 1, Guid? multiBuyId = null, string? selectedVariant = null)
|
||||||
{
|
{
|
||||||
var existingItem = Items.FirstOrDefault(i =>
|
var existingItem = Items.FirstOrDefault(i =>
|
||||||
@ -96,7 +155,8 @@ namespace TeleBot.Models
|
|||||||
{
|
{
|
||||||
public Guid ProductId { get; set; }
|
public Guid ProductId { get; set; }
|
||||||
public Guid? MultiBuyId { get; set; } // For quantity pricing (e.g., 3 for £25)
|
public Guid? MultiBuyId { get; set; } // For quantity pricing (e.g., 3 for £25)
|
||||||
public string? SelectedVariant { get; set; } // For color/flavor selection
|
public string? SelectedVariant { get; set; } // For single items - one variant
|
||||||
|
public List<string> SelectedVariants { get; set; } = new(); // For multi-buys - multiple variants
|
||||||
public string ProductName { get; set; } = string.Empty;
|
public string ProductName { get; set; } = string.Empty;
|
||||||
public int Quantity { get; set; }
|
public int Quantity { get; set; }
|
||||||
public decimal UnitPrice { get; set; } // For multi-buys, this is the bundle price; for regular items, it's per-unit
|
public decimal UnitPrice { get; set; } // For multi-buys, this is the bundle price; for regular items, it's per-unit
|
||||||
|
|||||||
@ -92,6 +92,7 @@ namespace TeleBot.Models
|
|||||||
BrowsingCategories,
|
BrowsingCategories,
|
||||||
ViewingProducts,
|
ViewingProducts,
|
||||||
ViewingProduct,
|
ViewingProduct,
|
||||||
|
SelectingVariants,
|
||||||
ViewingCart,
|
ViewingCart,
|
||||||
CheckoutFlow,
|
CheckoutFlow,
|
||||||
ViewingOrders,
|
ViewingOrders,
|
||||||
|
|||||||
@ -299,12 +299,37 @@ namespace TeleBot.Services
|
|||||||
var caption = $"🛍️ *{product.Name}*\n";
|
var caption = $"🛍️ *{product.Name}*\n";
|
||||||
caption += $"💰 *${product.Price:F2}*\n";
|
caption += $"💰 *${product.Price:F2}*\n";
|
||||||
|
|
||||||
|
// Show multi-buy deals if available
|
||||||
|
if (product.MultiBuys != null && product.MultiBuys.Any(mb => mb.IsActive))
|
||||||
|
{
|
||||||
|
caption += "\n🏷️ *Special Offers:*\n";
|
||||||
|
foreach (var multibuy in product.MultiBuys.Where(mb => mb.IsActive).OrderBy(mb => mb.Quantity))
|
||||||
|
{
|
||||||
|
caption += $" • {multibuy.Name}: ${multibuy.Price:F2} (${multibuy.PricePerUnit:F2} each)\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show available variants
|
||||||
|
if (product.Variants != null && product.Variants.Any(v => v.IsActive))
|
||||||
|
{
|
||||||
|
var variantTypes = product.Variants
|
||||||
|
.Where(v => v.IsActive)
|
||||||
|
.GroupBy(v => v.VariantType)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var group in variantTypes)
|
||||||
|
{
|
||||||
|
var variantNames = string.Join(", ", group.OrderBy(v => v.SortOrder).Select(v => v.Name));
|
||||||
|
caption += $"\n🎨 *{group.Key}:* {variantNames}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(product.Description))
|
if (!string.IsNullOrEmpty(product.Description))
|
||||||
{
|
{
|
||||||
var desc = product.Description.Length > 200
|
var desc = product.Description.Length > 200
|
||||||
? product.Description.Substring(0, 197) + "..."
|
? product.Description.Substring(0, 197) + "..."
|
||||||
: product.Description;
|
: product.Description;
|
||||||
caption += $"\n_{desc}_";
|
caption += $"\n\n_{desc}_";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(product.CategoryName))
|
if (!string.IsNullOrEmpty(product.CategoryName))
|
||||||
|
|||||||
@ -94,30 +94,42 @@ namespace TeleBot.UI
|
|||||||
{
|
{
|
||||||
var buttons = new List<InlineKeyboardButton[]>();
|
var buttons = new List<InlineKeyboardButton[]>();
|
||||||
|
|
||||||
|
// Check if product has variants
|
||||||
|
bool hasVariants = product.Variants?.Any(v => v.IsActive) == true;
|
||||||
|
|
||||||
// Show multi-buy options if available
|
// Show multi-buy options if available
|
||||||
if (product.MultiBuys?.Any() == true)
|
if (product.MultiBuys?.Any(mb => mb.IsActive) == true)
|
||||||
{
|
{
|
||||||
buttons.Add(new[] {
|
buttons.Add(new[] {
|
||||||
InlineKeyboardButton.WithCallbackData("💰 Multi-Buy Options:", "noop")
|
InlineKeyboardButton.WithCallbackData("💰 Multi-Buy Deals:", "noop")
|
||||||
});
|
});
|
||||||
|
|
||||||
foreach (var multiBuy in product.MultiBuys.OrderBy(mb => mb.Quantity))
|
foreach (var multiBuy in product.MultiBuys.Where(mb => mb.IsActive).OrderBy(mb => mb.Quantity))
|
||||||
{
|
{
|
||||||
var label = $"{multiBuy.Name} - £{multiBuy.Price:F2}";
|
var label = $"{multiBuy.Name} - ${multiBuy.Price:F2}";
|
||||||
if (multiBuy.Quantity > 1)
|
if (multiBuy.Quantity > 1)
|
||||||
label += $" (£{multiBuy.PricePerUnit:F2}/each)";
|
label += $" (${multiBuy.PricePerUnit:F2}/each)";
|
||||||
|
|
||||||
|
// If has variants, need variant selection first
|
||||||
|
var callbackData = hasVariants
|
||||||
|
? $"selectvar:{product.Id}:{multiBuy.Quantity}:{multiBuy.Id}"
|
||||||
|
: $"add:{product.Id}:{multiBuy.Quantity}:{multiBuy.Id}";
|
||||||
|
|
||||||
buttons.Add(new[]
|
buttons.Add(new[]
|
||||||
{
|
{
|
||||||
InlineKeyboardButton.WithCallbackData(label, $"add:{product.Id}:{multiBuy.Quantity}:{multiBuy.Id}")
|
InlineKeyboardButton.WithCallbackData(label, callbackData)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add regular single item option
|
// Add regular single item option
|
||||||
|
var singleCallbackData = hasVariants
|
||||||
|
? $"selectvar:{product.Id}:1"
|
||||||
|
: $"add:{product.Id}:1";
|
||||||
|
|
||||||
buttons.Add(new[] {
|
buttons.Add(new[] {
|
||||||
InlineKeyboardButton.WithCallbackData(
|
InlineKeyboardButton.WithCallbackData(
|
||||||
$"🛒 Single Item - £{product.Price:F2}",
|
$"🛒 Single Item - ${product.Price:F2}",
|
||||||
$"add:{product.Id}:1"
|
singleCallbackData
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -134,14 +146,38 @@ namespace TeleBot.UI
|
|||||||
buttons.Add(quantityButtons.ToArray());
|
buttons.Add(quantityButtons.ToArray());
|
||||||
|
|
||||||
// Add to cart button
|
// Add to cart button
|
||||||
|
var addCallbackData = hasVariants
|
||||||
|
? $"selectvar:{product.Id}:{quantity}"
|
||||||
|
: $"add:{product.Id}:{quantity}";
|
||||||
|
|
||||||
buttons.Add(new[] {
|
buttons.Add(new[] {
|
||||||
InlineKeyboardButton.WithCallbackData(
|
InlineKeyboardButton.WithCallbackData(
|
||||||
$"🛒 Add to Cart - £{product.Price:F2}",
|
$"🛒 Add to Cart - ${product.Price * quantity:F2}",
|
||||||
$"add:{product.Id}:{quantity}"
|
addCallbackData
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show variant info if available
|
||||||
|
if (hasVariants)
|
||||||
|
{
|
||||||
|
var variantTypes = product.Variants
|
||||||
|
.Where(v => v.IsActive)
|
||||||
|
.GroupBy(v => v.VariantType)
|
||||||
|
.Select(g => $"{g.Key}: {g.Count()} options")
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (variantTypes.Any())
|
||||||
|
{
|
||||||
|
buttons.Add(new[] {
|
||||||
|
InlineKeyboardButton.WithCallbackData(
|
||||||
|
$"🎨 Variants: {string.Join(", ", variantTypes)}",
|
||||||
|
"noop"
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
buttons.Add(new[] { InlineKeyboardButton.WithCallbackData("⬅️ Back to Products", "browse") });
|
buttons.Add(new[] { InlineKeyboardButton.WithCallbackData("⬅️ Back to Products", "browse") });
|
||||||
|
|
||||||
@ -294,6 +330,117 @@ namespace TeleBot.UI
|
|||||||
return new InlineKeyboardMarkup(buttons);
|
return new InlineKeyboardMarkup(buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static InlineKeyboardMarkup VariantSelectionMenu(Product product, int quantity, string? multiBuyId = null, List<string>? selectedVariants = null)
|
||||||
|
{
|
||||||
|
var buttons = new List<InlineKeyboardButton[]>();
|
||||||
|
selectedVariants ??= new List<string>();
|
||||||
|
|
||||||
|
if (product.Variants?.Any(v => v.IsActive) != true)
|
||||||
|
{
|
||||||
|
// No variants, just add to cart
|
||||||
|
var callbackData = multiBuyId != null
|
||||||
|
? $"add:{product.Id}:{quantity}:{multiBuyId}"
|
||||||
|
: $"add:{product.Id}:{quantity}";
|
||||||
|
|
||||||
|
buttons.Add(new[] {
|
||||||
|
InlineKeyboardButton.WithCallbackData($"🛒 Add to Cart", callbackData)
|
||||||
|
});
|
||||||
|
buttons.Add(new[] { InlineKeyboardButton.WithCallbackData("⬅️ Back", $"product:{product.Id}") });
|
||||||
|
return new InlineKeyboardMarkup(buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group variants by type
|
||||||
|
var variantGroups = product.Variants
|
||||||
|
.Where(v => v.IsActive)
|
||||||
|
.GroupBy(v => v.VariantType)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Show selection status for multi-buy
|
||||||
|
if (quantity > 1)
|
||||||
|
{
|
||||||
|
buttons.Add(new[] {
|
||||||
|
InlineKeyboardButton.WithCallbackData(
|
||||||
|
$"📦 Selecting for {quantity} items ({selectedVariants.Count} selected)",
|
||||||
|
"noop"
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each variant type, show options
|
||||||
|
foreach (var group in variantGroups)
|
||||||
|
{
|
||||||
|
buttons.Add(new[] {
|
||||||
|
InlineKeyboardButton.WithCallbackData($"Select {group.Key}:", "noop")
|
||||||
|
});
|
||||||
|
|
||||||
|
var variantButtons = new List<InlineKeyboardButton>();
|
||||||
|
foreach (var variant in group.OrderBy(v => v.SortOrder))
|
||||||
|
{
|
||||||
|
// For multi-buy, allow multiple selections
|
||||||
|
if (quantity > 1)
|
||||||
|
{
|
||||||
|
var count = selectedVariants.Count(v => v == variant.Name);
|
||||||
|
var buttonText = count > 0 ? $"{variant.Name} ({count})" : variant.Name;
|
||||||
|
|
||||||
|
variantButtons.Add(InlineKeyboardButton.WithCallbackData(
|
||||||
|
buttonText,
|
||||||
|
$"addvariant:{product.Id}:{quantity}:{variant.Name}:{multiBuyId ?? "null"}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Single item, select one variant
|
||||||
|
var isSelected = selectedVariants.Contains(variant.Name);
|
||||||
|
var buttonText = isSelected ? $"✅ {variant.Name}" : variant.Name;
|
||||||
|
|
||||||
|
variantButtons.Add(InlineKeyboardButton.WithCallbackData(
|
||||||
|
buttonText,
|
||||||
|
$"setvariant:{product.Id}:{variant.Name}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 confirm button when selections are complete
|
||||||
|
bool canConfirm = quantity == 1 ? selectedVariants.Count == 1 : selectedVariants.Count == quantity;
|
||||||
|
|
||||||
|
if (canConfirm)
|
||||||
|
{
|
||||||
|
var variantString = string.Join(",", selectedVariants);
|
||||||
|
var callbackData = multiBuyId != null
|
||||||
|
? $"confirmvar:{product.Id}:{quantity}:{multiBuyId}:{variantString}"
|
||||||
|
: $"confirmvar:{product.Id}:{quantity}:null:{variantString}";
|
||||||
|
|
||||||
|
buttons.Add(new[] {
|
||||||
|
InlineKeyboardButton.WithCallbackData(
|
||||||
|
$"✅ Confirm & Add to Cart",
|
||||||
|
callbackData
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear selection button for multi-buy
|
||||||
|
if (quantity > 1 && selectedVariants.Any())
|
||||||
|
{
|
||||||
|
buttons.Add(new[] {
|
||||||
|
InlineKeyboardButton.WithCallbackData(
|
||||||
|
"🔄 Clear Selections",
|
||||||
|
$"selectvar:{product.Id}:{quantity}:{multiBuyId ?? "null"}"
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.Add(new[] { InlineKeyboardButton.WithCallbackData("⬅️ Back", $"product:{product.Id}") });
|
||||||
|
|
||||||
|
return new InlineKeyboardMarkup(buttons);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetCurrencyEmoji(string currency)
|
private static string GetCurrencyEmoji(string currency)
|
||||||
{
|
{
|
||||||
return currency.ToUpper() switch
|
return currency.ToUpper() switch
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user