using Spry; using Inversion; using Astralis; using Invercargill; using Invercargill.DataStructures; namespace Spry.Users.Components { /** * UserFormComponent - Modal form for creating and editing users. * * This component provides: * - Modal dialog for create/edit operations * - Form fields: username, email, password (create only), confirm password * - Validation with error messages * - Permission editor integration * - HTMX-based submission * * The component can operate in two modes: * - Create mode: All fields including password are required * - Edit mode: Password is optional; only set if changing * * Usage: * // Create mode: * var form = factory.create(); * form.show_create(); * * // Edit mode: * var form = factory.create(); * form.set_user(existing_user); */ public class UserFormComponent : Component { private UserService user_service = inject(); private ComponentFactory factory = inject(); private HttpContext http_context = inject(); // ========================================================================= // State Properties // ========================================================================= private User? _editing_user = null; private bool _is_visible = false; private string? _error_message = null; private string? _success_message = null; // ========================================================================= // Configuration Properties // ========================================================================= /** * Whether to display as a modal dialog. * Default: true */ public bool is_modal { get; set; default = true; } // ========================================================================= // Public API // ========================================================================= /** * Sets the user to edit and shows the form in edit mode. * * @param user The user to edit */ public void set_user(User user) { _editing_user = user; _is_visible = true; _error_message = null; _success_message = null; } /** * Shows the form in create mode. */ public void show_create() { _editing_user = null; _is_visible = true; _error_message = null; _success_message = null; } /** * Hides the form and clears state. */ public void hide() { _is_visible = false; _editing_user = null; _error_message = null; _success_message = null; } /** * Returns true if in create mode (not editing an existing user). */ public bool is_creating { get { return _editing_user == null; } } /** * Returns true if the form is currently visible. */ public bool visible { get { return _is_visible; } } /** * Gets the current error message, if any. */ public string? error_message { get { return _error_message; } } // ========================================================================= // Component Implementation // ========================================================================= public override string markup { get { return """
"""; }} public override async void prepare() throws Error { if (!_is_visible) { return; } // Pre-populate form fields if editing if (_editing_user != null) { this["user-id"].set_attribute("value", _editing_user.id); this["form-username"].set_attribute("value", _editing_user.username); this["form-email"].set_attribute("value", _editing_user.email); // Set up permission editor with user's current permissions var perm_editor = get_component_child("permission-editor"); perm_editor.set_permissions(_editing_user.permissions); } else { // Clear permission editor for create mode var perm_editor = get_component_child("permission-editor"); perm_editor.clear_all(); } } public async override void handle_action(string action) throws Error { switch (action) { case "Save": yield save_user(); break; case "Cancel": hide(); break; } } // ========================================================================= // Private Helpers // ========================================================================= private async void save_user() throws Error { var query = http_context.request.query_params; // Get form values var user_id = get_query_value(query, "user_id"); var username = get_query_value(query, "username").strip(); var email = get_query_value(query, "email").strip(); // Validate username if (username.length < 3) { _error_message = "Username must be at least 3 characters"; return; } // Validate username format (alphanumeric + underscore) if (!is_valid_username(username)) { _error_message = "Username can only contain letters, numbers, and underscores"; return; } // Validate email if (email.length == 0) { _error_message = "Email is required"; return; } if (!is_valid_email(email)) { _error_message = "Please enter a valid email address"; return; } // Get permissions from editor var perm_editor = get_component_child("permission-editor"); var permissions = perm_editor.get_selected(); try { if (user_id.length == 0) { // Create new user var password = get_query_value(query, "password"); if (password.length < 8) { _error_message = "Password must be at least 8 characters"; return; } string? create_error; var user = user_service.create_user(username, email, password, out create_error); if (user == null) { _error_message = create_error ?? "Failed to create user"; return; } // Set permissions if any were selected if (permissions.length > 0) { user.permissions = permissions; string? update_error; if (!user_service.update_user(user, out update_error)) { _error_message = update_error ?? "Failed to set permissions"; return; } } _success_message = "User created successfully"; hide(); } else { // Update existing user var user = user_service.get_user(user_id); if (user == null) { _error_message = "User not found"; return; } // Update fields user.username = username; user.email = email; user.permissions = permissions; // Update password if provided var new_password = get_query_value(query, "new_password"); if (new_password.length > 0) { if (new_password.length < 8) { _error_message = "New password must be at least 8 characters"; return; } string? password_error; if (!user_service.set_password(user, new_password, out password_error)) { _error_message = password_error ?? "Failed to update password"; return; } } // Save changes string? update_error; if (!user_service.update_user(user, out update_error)) { _error_message = update_error ?? "Failed to update user"; return; } _success_message = "User updated successfully"; hide(); } } catch (Error e) { _error_message = "Error: %s".printf(e.message); } } private string get_query_value(Catalogue query, string key) { var value = query.get_any_or_default(key); return value != null ? ((!)value).strip() : ""; } private bool is_valid_username(string username) { if (username.length == 0) return false; foreach (var c in username.data) { if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) { return false; } } return true; } private bool is_valid_email(string email) { // Basic email validation: contains @ and at least one . after @ var at_index = email.index_of("@"); if (at_index < 1) return false; var dot_index = email.index_of(".", at_index); return dot_index > at_index + 1 && dot_index < email.length - 1; } } }