212 lines
7.6 KiB
GDScript
212 lines
7.6 KiB
GDScript
extends Control
|
|
## Main menu with login/register UI wired to the backend auth API.
|
|
##
|
|
## The AuthPanel is hidden by default. When the user clicks the LoginButton or
|
|
## RegisterButton (AuthButtonsRow, above the panel), the auth panel slides in.
|
|
## The Login and Register buttons on the AuthButtonsRow directly set the panel's
|
|
## mode, so the user can switch between Login and Register at any time while
|
|
## the panel is open.
|
|
|
|
# ─── UI references ────────────────────────────────────────────────────────────
|
|
|
|
var _auth_buttons_row: HBoxContainer
|
|
var _login_button: Button
|
|
var _register_button: Button
|
|
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 _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_buttons_row = $CenterContainer/VBoxContainer/AuthButtonsRow
|
|
_login_button = $CenterContainer/VBoxContainer/AuthButtonsRow/LoginButton
|
|
_register_button = $CenterContainer/VBoxContainer/AuthButtonsRow/RegisterButton
|
|
_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
|
|
_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
|
|
# Browse worlds is the first real screen past login -- actual gameplay
|
|
# (joining a world) doesn't exist yet, browsing does.
|
|
get_tree().change_scene_to_file("res://scenes/world_list.tscn")
|
|
|
|
|
|
func _on_quit_button_pressed() -> void:
|
|
get_tree().quit()
|
|
|
|
|
|
# ─── Auth panel toggle ────────────────────────────────────────────────────────
|
|
|
|
func _on_login_button_pressed() -> void:
|
|
"""Show the auth panel in login mode, called from AuthButtonsRow/LoginButton."""
|
|
_auth_panel.visible = true
|
|
_is_register_mode = false
|
|
_update_auth_mode_ui()
|
|
_clear_status()
|
|
_username_edit.grab_focus()
|
|
|
|
|
|
func _on_register_button_pressed() -> void:
|
|
"""Show the auth panel in register mode, called from AuthButtonsRow/RegisterButton."""
|
|
_auth_panel.visible = true
|
|
_is_register_mode = true
|
|
_update_auth_mode_ui()
|
|
_clear_status()
|
|
_username_edit.grab_focus()
|
|
|
|
|
|
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, hide the now-redundant
|
|
# Login/Register entry points (the auth panel itself stays open,
|
|
# showing the welcome message in place of the form).
|
|
_welcome_label.text = "Welcome, %s!" % _username_edit.text.strip_edges()
|
|
_welcome_label.visible = true
|
|
_status_label.visible = false
|
|
_play_button.disabled = false
|
|
_auth_buttons_row.visible = false
|
|
print("Auth successful. Token stored.")
|
|
else:
|
|
# Failure — show backend error detail.
|
|
_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
|