434 lines
12 KiB
C#
434 lines
12 KiB
C#
using System.Windows.Input;
|
|
using IronServices.Client;
|
|
|
|
namespace IronServices.Maui.Controls;
|
|
|
|
/// <summary>
|
|
/// A reusable login view for IronServices authentication.
|
|
/// Supports both MVVM command bindings and event-based usage.
|
|
/// </summary>
|
|
public partial class LoginView : ContentView
|
|
{
|
|
private IronServicesClient? _client;
|
|
|
|
public LoginView()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
#region Bindable Properties - Configuration
|
|
|
|
/// <summary>
|
|
/// The IronServicesClient to use for authentication (event-based mode).
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The title text displayed above the login form.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The subtitle text displayed below the title.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The logo image source.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Text to display in the logo badge (e.g., "IL" for IronLicensing).
|
|
/// Used when Logo is not set.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Background color for the logo badge.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Footer text displayed below the form.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Maximum width of the form. Default is -1 (no limit).
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether to show the register link.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether to show the forgot password link.
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// The email address entered by the user.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The password entered by the user.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Error message to display.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether a login operation is in progress.
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Command to execute when the login button is pressed.
|
|
/// If set, takes precedence over event-based login.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Command to execute when the register link is tapped.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Command to execute when the forgot password link is tapped.
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Raised when login is successful (event-based mode only).
|
|
/// </summary>
|
|
public event EventHandler<LoginSuccessEventArgs>? LoginSuccess;
|
|
|
|
/// <summary>
|
|
/// Raised when login fails (event-based mode only).
|
|
/// </summary>
|
|
public event EventHandler<LoginFailedEventArgs>? LoginFailed;
|
|
|
|
/// <summary>
|
|
/// Raised when the register link is tapped (if RegisterCommand is not set).
|
|
/// </summary>
|
|
public event EventHandler? RegisterRequested;
|
|
|
|
/// <summary>
|
|
/// Raised when the forgot password link is tapped (if ForgotPasswordCommand is not set).
|
|
/// </summary>
|
|
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
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event args for successful login.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event args for failed login.
|
|
/// </summary>
|
|
public class LoginFailedEventArgs : EventArgs
|
|
{
|
|
public string Error { get; }
|
|
|
|
public LoginFailedEventArgs(string error)
|
|
{
|
|
Error = error;
|
|
}
|
|
}
|