use actix_session::Session; use serde::{Serialize, Deserialize}; use rust_decimal::Decimal; use crate::services::{currency::CurrencyService, user_persistence::UserPersistence}; /// Service for handling navbar dropdown data #[derive(Clone)] pub struct NavbarService { currency_service: CurrencyService, } /// Data structure for navbar dropdown menu #[derive(Debug, Serialize, Deserialize)] pub struct NavbarDropdownData { pub user_name: Option, pub user_email: String, pub wallet_balance: Decimal, pub wallet_balance_formatted: String, pub display_currency: String, pub currency_symbol: String, pub quick_actions: Vec, pub show_topup_button: bool, } #[derive(Debug, Serialize, Deserialize)] pub struct QuickAction { pub id: String, pub label: String, pub url: String, pub icon: String, pub badge: Option, } /// Builder for NavbarService #[derive(Default)] pub struct NavbarServiceBuilder { currency_service: Option, } impl NavbarServiceBuilder { pub fn new() -> Self { Self::default() } pub fn with_currency_service(mut self, service: CurrencyService) -> Self { self.currency_service = Some(service); self } pub fn build(self) -> Result { let currency_service = self.currency_service .unwrap_or_else(|| CurrencyService::builder().build().unwrap()); Ok(NavbarService { currency_service, }) } } impl NavbarService { pub fn builder() -> NavbarServiceBuilder { NavbarServiceBuilder::new() } /// Get navbar dropdown data for authenticated user pub fn get_dropdown_data(&self, session: &Session) -> Result { // Get user email from session let user_email = session.get::("user_email") .map_err(|e| format!("Failed to get user email from session: {}", e))? .ok_or("User not authenticated")?; // Load user persistent data let persistent_data = UserPersistence::load_user_data(&user_email) .unwrap_or_else(|| crate::services::user_persistence::UserPersistentData { user_email: user_email.clone(), ..Default::default() }); // Get user's preferred display currency let mut display_currency = self.currency_service.get_user_preferred_currency(session); // Get currency info for formatting; fall back to USD if invalid let (currency, effective_currency) = match self.currency_service.get_currency(&display_currency) { Some(c) => (c, display_currency.clone()), None => { let usd = self .currency_service .get_currency("USD") .expect("USD currency must be available"); display_currency = "USD".to_string(); (usd, "USD".to_string()) } }; // Convert wallet balance to display currency let wallet_balance_display = if effective_currency == "USD" { persistent_data.wallet_balance_usd } else { self.currency_service.convert_amount( persistent_data.wallet_balance_usd, "USD", &effective_currency, ).unwrap_or(Decimal::ZERO) }; // Format the balance let wallet_balance_formatted = currency.format_amount(wallet_balance_display); // Create quick actions let quick_actions = vec![ QuickAction { id: "topup".to_string(), label: "Top Up Wallet".to_string(), url: "/wallet?action=topup".to_string(), icon: "bi-plus-circle".to_string(), badge: None, }, QuickAction { id: "wallet".to_string(), label: "Wallet".to_string(), url: "/wallet".to_string(), icon: "bi-wallet2".to_string(), badge: None, }, QuickAction { id: "settings".to_string(), label: "Settings".to_string(), url: "/dashboard/settings".to_string(), icon: "bi-gear".to_string(), badge: None, }, ]; Ok(NavbarDropdownData { user_name: persistent_data.name, user_email, wallet_balance: wallet_balance_display, wallet_balance_formatted, display_currency: effective_currency.clone(), currency_symbol: currency.symbol.clone(), quick_actions, show_topup_button: true, }) } /// Get simplified navbar data for guest users pub fn get_guest_data() -> NavbarDropdownData { NavbarDropdownData { user_name: None, user_email: String::new(), wallet_balance: Decimal::ZERO, wallet_balance_formatted: "Not logged in".to_string(), display_currency: "USD".to_string(), currency_symbol: "$".to_string(), quick_actions: vec![ QuickAction { id: "login".to_string(), label: "Login".to_string(), url: "/login".to_string(), icon: "bi-box-arrow-in-right".to_string(), badge: None, }, QuickAction { id: "register".to_string(), label: "Register".to_string(), url: "/register".to_string(), icon: "bi-person-plus".to_string(), badge: None, }, ], show_topup_button: false, } } /// Check if user has sufficient balance for a purchase pub fn check_sufficient_balance(&self, session: &Session, required_amount_usd: Decimal) -> Result { let user_email = session.get::("user_email") .map_err(|e| format!("Failed to get user email: {}", e))? .ok_or("User not authenticated")?; let persistent_data = UserPersistence::load_user_data(&user_email) .unwrap_or_default(); Ok(persistent_data.wallet_balance_usd >= required_amount_usd) } /// Get quick top-up amounts for user's preferred currency pub fn get_quick_topup_amounts(&self, session: &Session) -> Result, String> { let user_email = session.get::("user_email") .map_err(|e| format!("Failed to get user email: {}", e))? .ok_or("User not authenticated")?; let persistent_data = UserPersistence::load_user_data(&user_email) .unwrap_or_default(); // Use custom amounts if set, otherwise use defaults if let Some(custom_amounts) = persistent_data.quick_topup_amounts { Ok(custom_amounts) } else { // Default amounts in user's preferred currency let display_currency = self.currency_service.get_user_preferred_currency(session); let default_amounts = if display_currency == "USD" { vec![ Decimal::from(10), // $10 Decimal::from(25), // $25 Decimal::from(50), // $50 Decimal::from(100), // $100 ] } else { // Default fiat amounts (will be converted to USD internally) vec![ Decimal::from(10), // $10, €10, etc. Decimal::from(20), // $20, €20, etc. Decimal::from(50), // $50, €50, etc. Decimal::from(100), // $100, €100, etc. ] }; Ok(default_amounts) } } } impl Default for NavbarService { fn default() -> Self { Self::builder().build().unwrap() } }