diff --git a/src/commands/mod.rs b/src/commands/mod.rs
index 7d67043..ae5359e 100644
--- a/src/commands/mod.rs
+++ b/src/commands/mod.rs
@@ -3,9 +3,9 @@ pub mod my_bids;
pub mod my_listings;
pub mod new_listing;
pub mod settings;
-pub mod start;
+mod start;
pub use help::handle_help;
pub use my_bids::handle_my_bids;
pub use settings::handle_settings;
-pub use start::handle_start;
+pub use start::{enter_main_menu, handle_main_menu_callback, handle_start};
diff --git a/src/commands/my_listings.rs b/src/commands/my_listings.rs
index 7292669..4a6eb4e 100644
--- a/src/commands/my_listings.rs
+++ b/src/commands/my_listings.rs
@@ -1,6 +1,9 @@
use crate::{
case,
- commands::new_listing::{enter_edit_listing_draft, ListingDraft},
+ commands::{
+ enter_main_menu,
+ new_listing::{enter_edit_listing_draft, ListingDraft},
+ },
db::{listing::PersistedListing, user::PersistedUser, ListingDAO, ListingDbId, UserDAO},
keyboard_buttons,
message_utils::{extract_callback_data, pluralize_with_count, send_message, MessageTarget},
@@ -38,6 +41,12 @@ keyboard_buttons! {
}
}
+keyboard_buttons! {
+ enum MyListingsButtons {
+ BackToMenu("⬅️ Back to Menu", "my_listings_back_to_menu"),
+ }
+}
+
pub fn my_listings_handler() -> Handler<'static, HandlerResult, DpHandlerDescription> {
dptree::entry()
.branch(
@@ -74,7 +83,7 @@ async fn handle_my_listings_command_input(
Ok(())
}
-async fn show_listings_for_user(
+pub async fn show_listings_for_user(
db_pool: SqlitePool,
dialogue: RootDialogue,
bot: Bot,
@@ -101,13 +110,17 @@ async fn show_listings_for_user(
let listings = ListingDAO::find_by_seller(&db_pool, user.persisted.id).await?;
if listings.is_empty() {
+ // Create keyboard with just the back button
+ let keyboard =
+ teloxide::types::InlineKeyboardMarkup::new([[MyListingsButtons::BackToMenu.into()]]);
+
send_message(
&bot,
target,
"📋 My Listings\n\n\
You don't have any listings yet.\n\
Use /newlisting to create your first listing!",
- None,
+ Some(keyboard),
)
.await?;
return Ok(());
@@ -122,6 +135,9 @@ async fn show_listings_for_user(
)]);
}
+ // Add back to menu button
+ keyboard = keyboard.append_row(vec![MyListingsButtons::BackToMenu.into()]);
+
let response = format!(
"📋 My Listings\n\n\
You have {}.\n\n\
@@ -142,6 +158,18 @@ async fn handle_viewing_listings_callback(
let (data, from, message_id) = extract_callback_data(&bot, callback_query).await?;
let target = (from.clone(), message_id);
+ // Check if it's the back to menu button
+ if let Ok(button) = MyListingsButtons::try_from(data.as_str()) {
+ match button {
+ MyListingsButtons::BackToMenu => {
+ // Transition back to main menu using the reusable function
+ enter_main_menu(bot, dialogue, target).await?;
+ return Ok(());
+ }
+ }
+ }
+
+ // Otherwise, treat it as a listing ID
let listing_id = ListingDbId::new(data.parse::()?);
let (_, listing) =
get_user_and_listing(&db_pool, &bot, from.id, listing_id, target.clone()).await?;
@@ -190,10 +218,7 @@ async fn handle_managing_listing_callback(
let (data, from, message_id) = extract_callback_data(&bot, callback_query).await?;
let target = (from.clone(), message_id);
- let button = ManageListingButtons::try_from(data.as_str())
- .map_err(|_| anyhow::anyhow!("Invalid ManageListingButtons callback data: {}", data))?;
-
- match button {
+ match ManageListingButtons::try_from(data.as_str())? {
ManageListingButtons::Edit => {
let (_, listing) =
get_user_and_listing(&db_pool, &bot, from.id, listing_id, target.clone()).await?;
diff --git a/src/commands/new_listing/callbacks.rs b/src/commands/new_listing/callbacks.rs
index 6d749f8..59205be 100644
--- a/src/commands/new_listing/callbacks.rs
+++ b/src/commands/new_listing/callbacks.rs
@@ -4,20 +4,77 @@
//! in the new listing creation and editing workflows.
use crate::{
- commands::new_listing::{
- field_processing::transition_to_field,
- keyboard::*,
- messages::{get_keyboard_for_field, get_step_message},
- types::{ListingDraft, ListingDraftPersisted, ListingField, NewListingState},
- ui::show_confirmation_screen,
+ commands::{
+ new_listing::{
+ field_processing::transition_to_field,
+ keyboard::{
+ DurationKeyboardButtons, ListingTypeKeyboardButtons, SlotsKeyboardButtons,
+ StartTimeKeyboardButtons,
+ },
+ messages::{get_keyboard_for_field, get_step_message},
+ types::{ListingDraft, ListingDraftPersisted, ListingField, NewListingState},
+ ui::show_confirmation_screen,
+ },
+ start::enter_main_menu,
},
- db::{listing::ListingFields, ListingDuration},
+ db::{listing::ListingFields, ListingDuration, ListingType, UserDbId},
message_utils::*,
HandlerResult, RootDialogue,
};
use log::{error, info};
use teloxide::{types::CallbackQuery, Bot};
+/// Handle callbacks during the listing type selection phase
+pub async fn handle_selecting_listing_type_callback(
+ bot: Bot,
+ dialogue: RootDialogue,
+ seller_id: UserDbId,
+ callback_query: CallbackQuery,
+) -> HandlerResult {
+ let (data, from, message_id) = extract_callback_data(&bot, callback_query).await?;
+ info!("User {from:?} selected listing type: {data:?}");
+ let target = (from, message_id);
+
+ // Parse the listing type from callback data
+ let (listing_type, type_name) = match ListingTypeKeyboardButtons::try_from(data.as_str())? {
+ ListingTypeKeyboardButtons::FixedPrice => {
+ (ListingType::FixedPriceListing, "Fixed Price Listing")
+ }
+ ListingTypeKeyboardButtons::BasicAuction => (ListingType::BasicAuction, "Basic Auction"),
+ ListingTypeKeyboardButtons::BlindAuction => (ListingType::BlindAuction, "Blind Auction"),
+ ListingTypeKeyboardButtons::MultiSlot => {
+ (ListingType::MultiSlotAuction, "Multi-Slot Auction")
+ }
+ ListingTypeKeyboardButtons::Back => {
+ enter_main_menu(bot, dialogue, target).await?;
+ return Ok(());
+ }
+ };
+
+ // Create draft with selected listing type
+ let draft = ListingDraft::new_for_seller_with_type(seller_id, listing_type);
+
+ // Transition to first field (Title)
+ transition_to_field(dialogue, ListingField::Title, draft).await?;
+
+ let response = format!(
+ "✅ {} selected!\n\n\
+ Let's create your listing step by step!\n\n{}",
+ type_name,
+ get_step_message(ListingField::Title)
+ );
+
+ send_message(
+ &bot,
+ target,
+ response,
+ get_keyboard_for_field(ListingField::Title),
+ )
+ .await?;
+
+ Ok(())
+}
+
/// Handle callbacks during the field input phase
pub async fn handle_awaiting_draft_field_callback(
bot: Bot,
@@ -38,14 +95,17 @@ pub async fn handle_awaiting_draft_field_callback(
ListingField::Description if data == "skip" => {
handle_description_skip_callback(&bot, dialogue, draft, target).await
}
- ListingField::Slots if data.starts_with("slots_") => {
- handle_slots_callback(&bot, dialogue, draft, &data, target).await
+ ListingField::Slots => {
+ let button = SlotsKeyboardButtons::try_from(data.as_str())?;
+ handle_slots_callback(&bot, dialogue, draft, button, target).await
}
- ListingField::StartTime if data.starts_with("start_time_") => {
- handle_start_time_callback(&bot, dialogue, draft, &data, target).await
+ ListingField::StartTime => {
+ let button = StartTimeKeyboardButtons::try_from(data.as_str())?;
+ handle_start_time_callback(&bot, dialogue, draft, button, target).await
}
- ListingField::Duration if data.starts_with("duration_") => {
- handle_duration_callback(&bot, dialogue, draft, &data, target).await
+ ListingField::Duration => {
+ let button = DurationKeyboardButtons::try_from(data.as_str())?;
+ handle_duration_callback(&bot, dialogue, draft, button, target).await
}
_ => {
error!("Unknown callback data for field {field:?}: {data}");
@@ -84,12 +144,10 @@ async fn handle_slots_callback(
bot: &Bot,
dialogue: RootDialogue,
mut draft: ListingDraft,
- data: &str,
+ button: SlotsKeyboardButtons,
target: impl Into,
) -> HandlerResult {
let target = target.into();
- let button = SlotsKeyboardButtons::try_from(data)
- .map_err(|_| anyhow::anyhow!("Unknown SlotsKeyboardButtons data: {}", data))?;
let num_slots = match button {
SlotsKeyboardButtons::OneSlot => 1,
SlotsKeyboardButtons::TwoSlots => 2,
@@ -124,12 +182,10 @@ async fn handle_start_time_callback(
bot: &Bot,
dialogue: RootDialogue,
mut draft: ListingDraft,
- data: &str,
+ button: StartTimeKeyboardButtons,
target: impl Into,
) -> HandlerResult {
let target = target.into();
- let button = StartTimeKeyboardButtons::try_from(data)
- .map_err(|_| anyhow::anyhow!("Unknown StartTimeKeyboardButtons data: {}", data))?;
let start_time = match button {
StartTimeKeyboardButtons::Now => ListingDuration::zero(),
};
@@ -163,11 +219,10 @@ async fn handle_duration_callback(
bot: &Bot,
dialogue: RootDialogue,
mut draft: ListingDraft,
- data: &str,
+ button: DurationKeyboardButtons,
target: impl Into,
) -> HandlerResult {
let target = target.into();
- let button = DurationKeyboardButtons::try_from(data).unwrap();
let duration = ListingDuration::days(match button {
DurationKeyboardButtons::OneDay => 1,
DurationKeyboardButtons::ThreeDays => 3,
diff --git a/src/commands/new_listing/handler_factory.rs b/src/commands/new_listing/handler_factory.rs
index 39c9b20..4c56e82 100644
--- a/src/commands/new_listing/handler_factory.rs
+++ b/src/commands/new_listing/handler_factory.rs
@@ -28,6 +28,12 @@ pub fn new_listing_handler() -> Handler {
)
.branch(
Update::filter_callback_query()
+ .branch(
+ case![DialogueRootState::NewListing(
+ NewListingState::SelectingListingType { seller_id }
+ )]
+ .endpoint(handle_selecting_listing_type_callback),
+ )
.branch(
case![DialogueRootState::NewListing(
NewListingState::AwaitingDraftField { field, draft }
diff --git a/src/commands/new_listing/handlers.rs b/src/commands/new_listing/handlers.rs
index 0a13dab..55aa25e 100644
--- a/src/commands/new_listing/handlers.rs
+++ b/src/commands/new_listing/handlers.rs
@@ -12,7 +12,8 @@ use crate::{
SlotsKeyboardButtons, StartTimeKeyboardButtons,
},
messages::{
- get_edit_success_message, get_keyboard_for_field, get_step_message, get_success_message,
+ get_edit_success_message, get_keyboard_for_field, get_listing_type_keyboard,
+ get_listing_type_selection_message, get_step_message, get_success_message,
},
types::{ListingDraft, ListingDraftPersisted, ListingField, NewListingState},
ui::{display_listing_summary, show_confirmation_screen},
@@ -29,38 +30,38 @@ use sqlx::SqlitePool;
use teloxide::{prelude::*, types::*, Bot};
/// Handle the /newlisting command - starts the dialogue
-pub async fn handle_new_listing_command(
+pub(super) async fn handle_new_listing_command(
db_pool: SqlitePool,
bot: Bot,
dialogue: RootDialogue,
msg: Message,
) -> HandlerResult {
- info!(
- "User {} started new fixed price listing wizard",
- HandleAndId::from_chat(&msg.chat),
- );
let user = msg.from.ok_or_else(|| anyhow::anyhow!("User not found"))?;
+ enter_handle_new_listing(db_pool, bot, dialogue, user, msg.chat).await?;
+ Ok(())
+}
+
+pub async fn enter_handle_new_listing(
+ db_pool: SqlitePool,
+ bot: Bot,
+ dialogue: RootDialogue,
+ user: User,
+ target: impl Into,
+) -> HandlerResult {
let user = UserDAO::find_or_create_by_telegram_user(&db_pool, user).await?;
- // Initialize the dialogue to Start state
+ // Initialize the dialogue to listing type selection state
dialogue
- .update(NewListingState::AwaitingDraftField {
- field: ListingField::Title,
- draft: ListingDraft::new_for_seller(user.persisted.id),
+ .update(NewListingState::SelectingListingType {
+ seller_id: user.persisted.id,
})
.await?;
- let response = format!(
- "🛍️ Creating New Fixed Price Listing\n\n\
- Let's create your fixed price listing step by step!\n\n{}",
- get_step_message(ListingField::Title)
- );
-
send_message(
&bot,
- msg.chat,
- response,
- get_keyboard_for_field(ListingField::Title),
+ target,
+ get_listing_type_selection_message(),
+ Some(get_listing_type_keyboard()),
)
.await?;
Ok(())
@@ -154,10 +155,7 @@ pub async fn handle_viewing_draft_callback(
let target = (from.clone(), message_id);
- let button = ConfirmationKeyboardButtons::try_from(data.as_str())
- .map_err(|_| anyhow::anyhow!("Unknown ConfirmationKeyboardButtons data: {}", data))?;
-
- match button {
+ match ConfirmationKeyboardButtons::try_from(data.as_str())? {
ConfirmationKeyboardButtons::Create | ConfirmationKeyboardButtons::Save => {
info!("User {target:?} confirmed listing creation");
save_listing(db_pool, bot, target, draft).await?;
@@ -197,11 +195,9 @@ pub async fn handle_editing_draft_callback(
) -> HandlerResult {
let (data, from, message_id) = extract_callback_data(&bot, callback_query).await?;
let target = (from, message_id);
- let button = FieldSelectionKeyboardButtons::try_from(data.as_str())
- .map_err(|e| anyhow::anyhow!("Invalid field selection button: {}", e))?;
-
info!("User {target:?} in editing screen, showing field selection");
+ let button = FieldSelectionKeyboardButtons::try_from(data.as_str())?;
if button == FieldSelectionKeyboardButtons::Done {
show_confirmation_screen(&bot, target, &draft).await?;
dialogue
@@ -219,7 +215,9 @@ pub async fn handle_editing_draft_callback(
FieldSelectionKeyboardButtons::Slots => ListingField::Slots,
FieldSelectionKeyboardButtons::StartTime => ListingField::StartTime,
FieldSelectionKeyboardButtons::Duration => ListingField::Duration,
- FieldSelectionKeyboardButtons::Done => unreachable!(),
+ FieldSelectionKeyboardButtons::Done => {
+ return Err(anyhow::anyhow!("Done button should not be used here"))
+ }
};
let value = get_current_field_value(&draft, field)?;
diff --git a/src/commands/new_listing/keyboard.rs b/src/commands/new_listing/keyboard.rs
index ad61b8b..c9d0d35 100644
--- a/src/commands/new_listing/keyboard.rs
+++ b/src/commands/new_listing/keyboard.rs
@@ -53,3 +53,17 @@ keyboard_buttons! {
Now("Now", "start_time_now"),
}
}
+
+keyboard_buttons! {
+ pub enum ListingTypeKeyboardButtons {
+ [
+ FixedPrice("🛍️ Fixed Price", "listing_type_fixed_price"),
+ BasicAuction("⏰ Basic Auction", "listing_type_basic_auction"),
+ ],
+ [
+ BlindAuction("🎭 Blind Auction", "listing_type_blind_auction"),
+ MultiSlot("🎯 Multi-Slot Auction", "listing_type_multi_slot"),
+ ],
+ [Back("🔙 Back", "listing_type_back"),]
+ }
+}
diff --git a/src/commands/new_listing/messages.rs b/src/commands/new_listing/messages.rs
index 46fb05d..f5246be 100644
--- a/src/commands/new_listing/messages.rs
+++ b/src/commands/new_listing/messages.rs
@@ -71,3 +71,18 @@ fn create_cancel_keyboard() -> InlineKeyboardMarkup {
fn create_skip_cancel_keyboard() -> InlineKeyboardMarkup {
create_multi_row_keyboard(&[&[("Skip", "skip"), ("Cancel", "cancel")]])
}
+
+/// Get the listing type selection message
+pub fn get_listing_type_selection_message() -> &'static str {
+ "🛍️ What type of listing would you like to create?\n\n\
+ 🛍️ Fixed Price: Set a fixed price for immediate purchase\n\
+ ⏰ Basic Auction: Traditional time-based auction with bidding\n\
+ 🎭 Blind Auction: Buyers submit sealed bids, you choose the winner\n\
+ 🎯 Multi-Slot Auction: Multiple items/winners in one auction\n\n\
+ Choose your listing type:"
+}
+
+/// Get the keyboard for listing type selection
+pub fn get_listing_type_keyboard() -> InlineKeyboardMarkup {
+ ListingTypeKeyboardButtons::to_keyboard()
+}
diff --git a/src/commands/new_listing/mod.rs b/src/commands/new_listing/mod.rs
index 17ac453..95df398 100644
--- a/src/commands/new_listing/mod.rs
+++ b/src/commands/new_listing/mod.rs
@@ -17,7 +17,7 @@ mod field_processing;
mod handler_factory;
mod handlers;
mod keyboard;
-mod messages;
+pub mod messages;
#[cfg(test)]
mod tests;
@@ -27,5 +27,5 @@ mod validations;
// Re-export the main handler for external use
pub use handler_factory::new_listing_handler;
-pub use handlers::enter_edit_listing_draft;
+pub use handlers::{enter_edit_listing_draft, enter_handle_new_listing};
pub use types::*;
diff --git a/src/commands/new_listing/types.rs b/src/commands/new_listing/types.rs
index 46cd6f0..9cc669a 100644
--- a/src/commands/new_listing/types.rs
+++ b/src/commands/new_listing/types.rs
@@ -1,10 +1,11 @@
use crate::{
db::{
listing::{
- FixedPriceListingFields, ListingBase, ListingFields, NewListingFields,
- PersistedListing, PersistedListingFields,
+ BasicAuctionFields, BlindAuctionFields, FixedPriceListingFields, ListingBase,
+ ListingFields, MultiSlotAuctionFields, NewListingFields, PersistedListing,
+ PersistedListingFields,
},
- MoneyAmount, UserDbId,
+ ListingType, MoneyAmount, UserDbId,
},
DialogueRootState,
};
@@ -19,7 +20,34 @@ pub struct ListingDraft {
}
impl ListingDraft {
- pub fn new_for_seller(seller_id: UserDbId) -> Self {
+ pub fn new_for_seller_with_type(seller_id: UserDbId, listing_type: ListingType) -> Self {
+ let fields = match listing_type {
+ ListingType::BasicAuction => ListingFields::BasicAuction(BasicAuctionFields {
+ starting_bid: MoneyAmount::default(),
+ buy_now_price: None,
+ min_increment: MoneyAmount::from_cents(100), // Default $1.00 increment
+ anti_snipe_minutes: Some(5),
+ }),
+ ListingType::MultiSlotAuction => {
+ ListingFields::MultiSlotAuction(MultiSlotAuctionFields {
+ starting_bid: MoneyAmount::default(),
+ buy_now_price: MoneyAmount::default(),
+ min_increment: Some(MoneyAmount::from_cents(100)), // Default $1.00 increment
+ slots_available: 1,
+ anti_snipe_minutes: 5,
+ })
+ }
+ ListingType::FixedPriceListing => {
+ ListingFields::FixedPriceListing(FixedPriceListingFields {
+ buy_now_price: MoneyAmount::default(),
+ slots_available: 1,
+ })
+ }
+ ListingType::BlindAuction => ListingFields::BlindAuction(BlindAuctionFields {
+ starting_bid: MoneyAmount::default(),
+ }),
+ };
+
Self {
has_changes: false,
persisted: ListingDraftPersisted::New(NewListingFields::default()),
@@ -28,10 +56,7 @@ impl ListingDraft {
title: "".to_string(),
description: None,
},
- fields: ListingFields::FixedPriceListing(FixedPriceListingFields {
- buy_now_price: MoneyAmount::default(),
- slots_available: 0,
- }),
+ fields,
}
}
@@ -64,6 +89,9 @@ pub enum ListingField {
// Dialogue state for the new listing wizard
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum NewListingState {
+ SelectingListingType {
+ seller_id: UserDbId,
+ },
AwaitingDraftField {
field: ListingField,
draft: ListingDraft,
diff --git a/src/commands/start.rs b/src/commands/start.rs
index 7a749e2..c83e3dd 100644
--- a/src/commands/start.rs
+++ b/src/commands/start.rs
@@ -1,24 +1,140 @@
use log::info;
-use teloxide::{prelude::*, types::Message, Bot};
+use teloxide::{
+ types::{CallbackQuery, Message},
+ utils::command::BotCommands,
+ Bot,
+};
-use crate::HandlerResult;
+use sqlx::SqlitePool;
-pub async fn handle_start(bot: Bot, msg: Message) -> HandlerResult {
- let welcome_message = "🎯 Welcome to Pawctioneer Bot! 🎯\n\n\
- This bot helps you participate in various types of auctions:\n\
- • Standard auctions with anti-sniping protection\n\
- • Multi-slot auctions (multiple winners)\n\
- • Fixed price sales\n\
- • Blind auctions\n\n\
- Use /help to see all available commands.\n\n\
- Ready to start your auction experience? 🚀";
+use crate::{
+ commands::{my_listings::show_listings_for_user, new_listing::enter_handle_new_listing},
+ keyboard_buttons,
+ message_utils::{extract_callback_data, send_message, MessageTarget},
+ Command, DialogueRootState, HandlerResult, RootDialogue,
+};
- info!(
- "User {} ({}) started the bot",
- msg.chat.username().unwrap_or("unknown"),
- msg.chat.id
- );
+keyboard_buttons! {
+ pub enum MainMenuButtons {
+ [
+ NewListing("🛍️ New Listing", "menu_new_listing"),
+ ],
+ [
+ MyListings("📋 My Listings", "menu_my_listings"),
+ MyBids("💰 My Bids", "menu_my_bids"),
+ ],
+ [
+ Settings("⚙️ Settings", "menu_settings"),
+ Help("❓ Help", "menu_help"),
+ ]
+ }
+}
+
+/// Get the main menu welcome message
+pub fn get_main_menu_message() -> &'static str {
+ "🎯 Welcome to Pawctioneer Bot! 🎯\n\n\
+ This bot helps you participate in various types of auctions:\n\
+ • Standard auctions with anti-sniping protection\n\
+ • Multi-slot auctions (multiple winners)\n\
+ • Fixed price sales\n\
+ • Blind auctions\n\n\
+ Choose an option below to get started! 🚀"
+}
+
+pub async fn handle_start(bot: Bot, dialogue: RootDialogue, msg: Message) -> HandlerResult {
+ enter_main_menu(bot, dialogue, msg.chat).await?;
+ Ok(())
+}
+
+/// Show the main menu with buttons
+pub async fn enter_main_menu(
+ bot: Bot,
+ dialogue: RootDialogue,
+ target: impl Into,
+) -> HandlerResult {
+ dialogue.update(DialogueRootState::MainMenu).await?;
+
+ send_message(
+ &bot,
+ target,
+ get_main_menu_message(),
+ Some(MainMenuButtons::to_keyboard()),
+ )
+ .await?;
+
+ Ok(())
+}
+
+pub async fn handle_main_menu_callback(
+ db_pool: SqlitePool,
+ bot: Bot,
+ dialogue: RootDialogue,
+ callback_query: CallbackQuery,
+) -> HandlerResult {
+ let (data, from, message_id) = extract_callback_data(&bot, callback_query).await?;
+ let target = MessageTarget::from((from.clone(), message_id));
+
+ info!(
+ "User {} selected main menu option: {}",
+ from.username.as_deref().unwrap_or("unknown"),
+ data
+ );
+
+ let button = MainMenuButtons::try_from(data.as_str())?;
+ match button {
+ MainMenuButtons::NewListing => {
+ enter_handle_new_listing(db_pool, bot, dialogue, from.clone(), target).await?;
+ }
+ MainMenuButtons::MyListings => {
+ // Call show_listings_for_user directly
+ show_listings_for_user(db_pool, dialogue, bot, from.id, target).await?;
+ }
+ MainMenuButtons::MyBids => {
+ send_message(
+ &bot,
+ target,
+ "💰 My Bids (Coming Soon)\n\n\
+ Here you'll be able to view:\n\
+ • Your active bids\n\
+ • Bid history\n\
+ • Won/lost auctions\n\
+ • Outbid notifications\n\n\
+ Feature in development! 🛠️",
+ Some(MainMenuButtons::to_keyboard()),
+ )
+ .await?;
+ }
+ MainMenuButtons::Settings => {
+ send_message(
+ &bot,
+ target,
+ "⚙️ Settings (Coming Soon)\n\n\
+ Here you'll be able to configure:\n\
+ • Notification preferences\n\
+ • Language settings\n\
+ • Default bid increments\n\
+ • Outbid alerts\n\n\
+ Feature in development! 🛠️",
+ Some(MainMenuButtons::to_keyboard()),
+ )
+ .await?;
+ }
+ MainMenuButtons::Help => {
+ let help_message = format!(
+ "📋 Available Commands:\n\n{}\n\n\
+ 📧 Support: Contact @admin for help\n\
+ 🔗 More info: Use individual commands to get started!",
+ Command::descriptions()
+ );
+ send_message(
+ &bot,
+ target,
+ help_message,
+ Some(MainMenuButtons::to_keyboard()),
+ )
+ .await?;
+ }
+ }
- bot.send_message(msg.chat.id, welcome_message).await?;
Ok(())
}
diff --git a/src/keyboard_utils.rs b/src/keyboard_utils.rs
index 05940a3..7d567d0 100644
--- a/src/keyboard_utils.rs
+++ b/src/keyboard_utils.rs
@@ -50,13 +50,13 @@ macro_rules! keyboard_buttons {
}
}
impl<'a> TryFrom<&'a str> for $name {
- type Error = &'a str;
+ type Error = anyhow::Error;
fn try_from(value: &'a str) -> Result {
match value {
$($(
$callback_data => Ok(Self::$variant),
)*)*
- _ => Err(value),
+ _ => anyhow::bail!("Unknown {name} button: {value}", name = stringify!($name)),
}
}
}
diff --git a/src/main.rs b/src/main.rs
index 553f5cb..eae9c84 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -62,6 +62,7 @@ pub enum Command {
enum DialogueRootState {
#[default]
Start,
+ MainMenu,
NewListing(NewListingState),
MyListings(MyListingsState),
}
@@ -91,6 +92,9 @@ async fn main() -> Result<()> {
.enter_dialogue::, DialogueRootState>()
.branch(new_listing_handler())
.branch(my_listings_handler())
+ .branch(Update::filter_callback_query().branch(
+ dptree::case![DialogueRootState::MainMenu].endpoint(handle_main_menu_callback),
+ ))
.branch(
Update::filter_message().branch(
dptree::entry()