extends Control ## Main menu with login/register UI wired to the backend auth API. ## ## The AuthPanel is hidden by default. When the user clicks "Login" or "Register" ## (the existing Play/Quit buttons), the auth panel slides in. The ModeToggle ## switches between Login and Register modes, showing/hiding the email and DOB ## fields accordingly. ## ## On successful auth the token is stored via ApiClient, the welcome label ## shows the username, and the Play button becomes enabled. # ── UI references ────────────────────────────────────────────────────────── var _auth_panel: PanelContainer var _auth_mode_label: Label var _username_edit: LineEdit var _password_edit: LineEdit var _email_edit: LineEdit var _dob_edit: LineEdit var _mode_toggle: CheckButton var _auth_button: Button var _status_label: Label var _welcome_label: Label var _play_button: Button var _quit_button: Button var _is_register_mode: bool = false var _is_authenticating: bool = false # ── Lifecycle ────────────────────────────────────────────────────────────── func _ready() -> void: # Cache node references. _auth_panel = $CenterContainer/VBoxContainer/AuthPanel _auth_mode_label = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/AuthModeLabel _username_edit = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/UsernameEdit _password_edit = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/PasswordEdit _email_edit = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/EmailEdit _dob_edit = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/DOBEdit _mode_toggle = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/ModeToggle _auth_button = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/AuthButton _status_label = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/AuthStatusLabel _welcome_label = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/WelcomeLabel _play_button = $CenterContainer/VBoxContainer/PlayButton _quit_button = $CenterContainer/VBoxContainer/QuitButton # Initial state. _auth_panel.visible = false _status_label.visible = false _welcome_label.visible = false _email_edit.visible = false _dob_edit.visible = false _play_button.disabled = true # Gate play behind successful auth. _update_auth_mode_ui() # ── Play / Quit ─────────────────────────────────────────────────────────── func _on_play_button_pressed() -> void: if _play_button.disabled: return # TODO: transition to the worlds / gameplay scene once it exists. print("Play pressed — transitioning to game") func _on_quit_button_pressed() -> void: get_tree().quit() # ── Auth panel toggle ───────────────────────────────────────────────────── func _on_login_button_pressed() -> void: """Show the auth panel (called from a future Login button on the main menu).""" _auth_panel.visible = true _auth_panel.grab_focus() func _on_register_button_pressed() -> void: """Show the auth panel in register mode.""" _auth_panel.visible = true _is_register_mode = true _mode_toggle.button_pressed = true _update_auth_mode_ui() _clear_status() # ── Mode toggle (Login / Register) ──────────────────────────────────────── func _on_mode_toggle_toggled(button_pressed: bool) -> void: _is_register_mode = button_pressed _update_auth_mode_ui() _clear_status() func _update_auth_mode_ui() -> void: """Update labels and field visibility based on login vs register mode.""" if _is_register_mode: _auth_mode_label.text = "Register" _auth_button.text = "Register" _email_edit.visible = true _dob_edit.visible = true else: _auth_mode_label.text = "Login" _auth_button.text = "Login" _email_edit.visible = false _dob_edit.visible = false # ── Auth submission ─────────────────────────────────────────────────────── func _on_auth_button_pressed() -> void: """Handle login or register button press.""" if _is_authenticating: return # Validate inputs. var username := _username_edit.text.strip_edges() var password := _password_edit.text if username == "": _show_status("Username is required.") return if password == "": _show_status("Password is required.") return if _is_register_mode: var email := _email_edit.text.strip_edges() var dob := _dob_edit.text.strip_edges() if email == "": _show_status("Email is required.") return if dob == "": _show_status("Date of birth is required.") return # Basic YYYY-MM-DD format check. if not _is_valid_date(dob): _show_status("Date of birth must be in YYYY-MM-DD format.") return _auth_button.disabled = true _auth_button.text = "Registering..." _is_authenticating = true _clear_status() var result := await ApiClient.register(username, email, password, dob) _on_auth_complete(result) else: _auth_button.disabled = true _auth_button.text = "Logging in..." _is_authenticating = true _clear_status() var result := await ApiClient.login(username, password) _on_auth_complete(result) func _on_auth_complete(result: Dictionary) -> void: """Handle the result of a login or register call.""" _auth_button.disabled = false _auth_button.text = "Register" if _is_register_mode else "Login" _is_authenticating = false if result.ok: # Success — show welcome, enable Play. _welcome_label.text = "Welcome, %s!" % _username_edit.text.strip_edges() _welcome_label.visible = true _status_label.visible = false _play_button.disabled = false print("Auth successful. Token stored.") else: # Failure — show backend error detail. _show_status(result.detail) # Clear token if somehow it was set but request wasn't ok. if not ApiClient.has_token(): pass # No token to clear. else: # Network failure or unexpected response. _show_status(result.detail) # ── Status helpers ───────────────────────────────────────────────────────── func _show_status(message: String) -> void: _status_label.text = message _status_label.visible = true func _clear_status() -> void: _status_label.text = "" _status_label.visible = false # ── Validation ───────────────────────────────────────────────────────────── func _is_valid_date(date_str: String) -> bool: """Check if a string looks like a valid YYYY-MM-DD date.""" if date_str.length() != 10: return false var parts := date_str.split("-") if parts.size() != 3: return false var year := parts[0].to_int() var month := parts[1].to_int() var day := parts[2].to_int() if month < 1 or month > 12: return false if day < 1 or day > 31: return false # Rough month-day validation. var days_in_month := [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] if day > days_in_month[month]: return false return true