using System.Windows.Input; using IronServices.Client; namespace IronServices.Maui.Controls; /// /// A reusable login view for IronServices authentication. /// Supports both MVVM command bindings and event-based usage. /// public partial class LoginView : ContentView { private IronServicesClient? _client; public LoginView() { InitializeComponent(); } #region Bindable Properties - Configuration /// /// The IronServicesClient to use for authentication (event-based mode). /// public static readonly BindableProperty ClientProperty = BindableProperty.Create( nameof(Client), typeof(IronServicesClient), typeof(LoginView), null, propertyChanged: (b, o, n) => ((LoginView)b)._client = n as IronServicesClient); public IronServicesClient? Client { get => (IronServicesClient?)GetValue(ClientProperty); set => SetValue(ClientProperty, value); } /// /// The title text displayed above the login form. /// public static readonly BindableProperty TitleProperty = BindableProperty.Create( nameof(Title), typeof(string), typeof(LoginView), "Sign In"); public string Title { get => (string)GetValue(TitleProperty); set => SetValue(TitleProperty, value); } /// /// The subtitle text displayed below the title. /// public static readonly BindableProperty SubtitleProperty = BindableProperty.Create( nameof(Subtitle), typeof(string), typeof(LoginView), "Enter your credentials to continue"); public string Subtitle { get => (string)GetValue(SubtitleProperty); set => SetValue(SubtitleProperty, value); } /// /// The logo image source. /// public static readonly BindableProperty LogoProperty = BindableProperty.Create( nameof(Logo), typeof(ImageSource), typeof(LoginView), null); public ImageSource? Logo { get => (ImageSource?)GetValue(LogoProperty); set => SetValue(LogoProperty, value); } /// /// Text to display in the logo badge (e.g., "IL" for IronLicensing). /// Used when Logo is not set. /// public static readonly BindableProperty LogoTextProperty = BindableProperty.Create( nameof(LogoText), typeof(string), typeof(LoginView), null); public string? LogoText { get => (string?)GetValue(LogoTextProperty); set => SetValue(LogoTextProperty, value); } /// /// Background color for the logo badge. /// public static readonly BindableProperty LogoBackgroundColorProperty = BindableProperty.Create( nameof(LogoBackgroundColor), typeof(Color), typeof(LoginView), null); public Color? LogoBackgroundColor { get => (Color?)GetValue(LogoBackgroundColorProperty); set => SetValue(LogoBackgroundColorProperty, value); } /// /// Footer text displayed below the form. /// public static readonly BindableProperty FooterTextProperty = BindableProperty.Create( nameof(FooterText), typeof(string), typeof(LoginView), null); public string? FooterText { get => (string?)GetValue(FooterTextProperty); set => SetValue(FooterTextProperty, value); } /// /// Maximum width of the form. Default is -1 (no limit). /// public static readonly BindableProperty FormMaxWidthProperty = BindableProperty.Create( nameof(FormMaxWidth), typeof(double), typeof(LoginView), -1d); public double FormMaxWidth { get => (double)GetValue(FormMaxWidthProperty); set => SetValue(FormMaxWidthProperty, value); } /// /// Whether to show the register link. /// public static readonly BindableProperty ShowRegisterLinkProperty = BindableProperty.Create( nameof(ShowRegisterLink), typeof(bool), typeof(LoginView), false); public bool ShowRegisterLink { get => (bool)GetValue(ShowRegisterLinkProperty); set => SetValue(ShowRegisterLinkProperty, value); } /// /// Whether to show the forgot password link. /// public static readonly BindableProperty ShowForgotPasswordLinkProperty = BindableProperty.Create( nameof(ShowForgotPasswordLink), typeof(bool), typeof(LoginView), false); public bool ShowForgotPasswordLink { get => (bool)GetValue(ShowForgotPasswordLinkProperty); set => SetValue(ShowForgotPasswordLinkProperty, value); } #endregion #region Bindable Properties - MVVM Data /// /// The email address entered by the user. /// public static readonly BindableProperty EmailProperty = BindableProperty.Create( nameof(Email), typeof(string), typeof(LoginView), string.Empty, BindingMode.TwoWay); public string Email { get => (string)GetValue(EmailProperty); set => SetValue(EmailProperty, value); } /// /// The password entered by the user. /// public static readonly BindableProperty PasswordProperty = BindableProperty.Create( nameof(Password), typeof(string), typeof(LoginView), string.Empty, BindingMode.TwoWay); public string Password { get => (string)GetValue(PasswordProperty); set => SetValue(PasswordProperty, value); } /// /// Error message to display. /// public static readonly BindableProperty ErrorMessageProperty = BindableProperty.Create( nameof(ErrorMessage), typeof(string), typeof(LoginView), string.Empty); public string ErrorMessage { get => (string)GetValue(ErrorMessageProperty); set => SetValue(ErrorMessageProperty, value); } /// /// Whether a login operation is in progress. /// public static readonly BindableProperty IsBusyProperty = BindableProperty.Create( nameof(IsBusy), typeof(bool), typeof(LoginView), false); public bool IsBusy { get => (bool)GetValue(IsBusyProperty); set => SetValue(IsBusyProperty, value); } #endregion #region Bindable Properties - Commands /// /// Command to execute when the login button is pressed. /// If set, takes precedence over event-based login. /// public static readonly BindableProperty LoginCommandProperty = BindableProperty.Create( nameof(LoginCommand), typeof(ICommand), typeof(LoginView), null); public ICommand? LoginCommand { get => (ICommand?)GetValue(LoginCommandProperty); set => SetValue(LoginCommandProperty, value); } /// /// Command to execute when the register link is tapped. /// public static readonly BindableProperty RegisterCommandProperty = BindableProperty.Create( nameof(RegisterCommand), typeof(ICommand), typeof(LoginView), null); public ICommand? RegisterCommand { get => (ICommand?)GetValue(RegisterCommandProperty); set => SetValue(RegisterCommandProperty, value); } /// /// Command to execute when the forgot password link is tapped. /// public static readonly BindableProperty ForgotPasswordCommandProperty = BindableProperty.Create( nameof(ForgotPasswordCommand), typeof(ICommand), typeof(LoginView), null); public ICommand? ForgotPasswordCommand { get => (ICommand?)GetValue(ForgotPasswordCommandProperty); set => SetValue(ForgotPasswordCommandProperty, value); } #endregion #region Events /// /// Raised when login is successful (event-based mode only). /// public event EventHandler? LoginSuccess; /// /// Raised when login fails (event-based mode only). /// public event EventHandler? LoginFailed; /// /// Raised when the register link is tapped (if RegisterCommand is not set). /// public event EventHandler? RegisterRequested; /// /// Raised when the forgot password link is tapped (if ForgotPasswordCommand is not set). /// public event EventHandler? ForgotPasswordRequested; #endregion #region Event Handlers private async void OnLoginClicked(object sender, EventArgs e) { // If a command is bound, use it if (LoginCommand != null) { if (LoginCommand.CanExecute(null)) { LoginCommand.Execute(null); } return; } // Otherwise, use event-based login with Client if (_client == null) { ErrorMessage = "Client not configured"; return; } var email = Email?.Trim(); var password = Password; if (string.IsNullOrEmpty(email)) { ErrorMessage = "Please enter your email"; return; } if (string.IsNullOrEmpty(password)) { ErrorMessage = "Please enter your password"; return; } IsBusy = true; ErrorMessage = string.Empty; try { var result = await _client.LoginAsync(email, password); if (result.Success) { LoginSuccess?.Invoke(this, new LoginSuccessEventArgs(result)); } else { ErrorMessage = result.Error ?? "Login failed"; LoginFailed?.Invoke(this, new LoginFailedEventArgs(result.Error ?? "Login failed")); } } catch (Exception ex) { ErrorMessage = $"Error: {ex.Message}"; LoginFailed?.Invoke(this, new LoginFailedEventArgs(ex.Message)); } finally { IsBusy = false; } } private void OnRegisterTapped(object sender, EventArgs e) { if (RegisterCommand != null && RegisterCommand.CanExecute(null)) { RegisterCommand.Execute(null); } else { RegisterRequested?.Invoke(this, EventArgs.Empty); } } private void OnForgotPasswordTapped(object sender, EventArgs e) { if (ForgotPasswordCommand != null && ForgotPasswordCommand.CanExecute(null)) { ForgotPasswordCommand.Execute(null); } else { ForgotPasswordRequested?.Invoke(this, EventArgs.Empty); } } #endregion } /// /// Event args for successful login. /// public class LoginSuccessEventArgs : EventArgs { public LoginResult Result { get; } public string? UserId => Result.UserId; public string? Email => Result.Email; public string? DisplayName => Result.DisplayName; public LoginSuccessEventArgs(LoginResult result) { Result = result; } } /// /// Event args for failed login. /// public class LoginFailedEventArgs : EventArgs { public string Error { get; } public LoginFailedEventArgs(string error) { Error = error; } }