first commit
This commit is contained in:
209
godot_client/scenes/auth/login.gd
Normal file
209
godot_client/scenes/auth/login.gd
Normal file
@@ -0,0 +1,209 @@
|
||||
extends Control
|
||||
## Login Screen Script
|
||||
##
|
||||
## Handles user authentication via the backend API.
|
||||
## Validates input, displays errors, and navigates to character list on success.
|
||||
|
||||
# Node references
|
||||
@onready var email_input: LineEdit = $CenterContainer/LoginCard/MainContainer/ContentVBox/EmailInput
|
||||
@onready var password_input: LineEdit = $CenterContainer/LoginCard/MainContainer/ContentVBox/PasswordInput
|
||||
@onready var login_button: Button = $CenterContainer/LoginCard/MainContainer/ContentVBox/LoginButton
|
||||
@onready var error_label: Label = $CenterContainer/LoginCard/MainContainer/ContentVBox/ErrorLabel
|
||||
@onready var remember_checkbox: CheckBox = $CenterContainer/LoginCard/MainContainer/ContentVBox/RememberCheckBox
|
||||
@onready var register_link: Button = $CenterContainer/LoginCard/MainContainer/ContentVBox/BottomLinksVBox/RegisterLink
|
||||
@onready var forgot_password_link: Button = $CenterContainer/LoginCard/MainContainer/ContentVBox/BottomLinksVBox/ForgotPasswordLink
|
||||
|
||||
# Service references
|
||||
@onready var http_client: Node = get_node("/root/HTTPClient")
|
||||
@onready var state_manager: Node = get_node("/root/StateManager")
|
||||
|
||||
# Internal state
|
||||
var _is_loading: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
print("[LoginScreen] Initialized")
|
||||
|
||||
# Connect button signals
|
||||
login_button.pressed.connect(_on_login_button_pressed)
|
||||
register_link.pressed.connect(_on_register_link_pressed)
|
||||
forgot_password_link.pressed.connect(_on_forgot_password_link_pressed)
|
||||
|
||||
# Connect Enter key to submit
|
||||
email_input.text_submitted.connect(_on_input_submitted)
|
||||
password_input.text_submitted.connect(_on_input_submitted)
|
||||
|
||||
# Hide error label by default
|
||||
error_label.visible = false
|
||||
|
||||
# Load saved remember setting
|
||||
remember_checkbox.button_pressed = state_manager.get_setting("remember_login", true)
|
||||
|
||||
|
||||
## Handle login button press
|
||||
func _on_login_button_pressed() -> void:
|
||||
# Prevent double-click
|
||||
if _is_loading:
|
||||
return
|
||||
|
||||
# Get input values
|
||||
var email := email_input.text.strip_edges()
|
||||
var password := password_input.text
|
||||
|
||||
# Validate inputs
|
||||
var validation_error := _validate_inputs(email, password)
|
||||
if not validation_error.is_empty():
|
||||
_show_error(validation_error)
|
||||
return
|
||||
|
||||
# Start login process
|
||||
_start_login(email, password)
|
||||
|
||||
|
||||
## Handle Enter key press in input fields
|
||||
func _on_input_submitted(_text: String = "") -> void:
|
||||
_on_login_button_pressed()
|
||||
|
||||
|
||||
## Validate email and password
|
||||
func _validate_inputs(email: String, password: String) -> String:
|
||||
# Check if email is empty
|
||||
if email.is_empty():
|
||||
return "Please enter your email address"
|
||||
|
||||
# Check if password is empty
|
||||
if password.is_empty():
|
||||
return "Please enter your password"
|
||||
|
||||
# Basic email format validation (contains @ and .)
|
||||
if not email.contains("@") or not email.contains("."):
|
||||
return "Please enter a valid email address"
|
||||
|
||||
# Check minimum password length
|
||||
if password.length() < 6:
|
||||
return "Password must be at least 6 characters"
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
## Start login process
|
||||
func _start_login(email: String, password: String) -> void:
|
||||
_is_loading = true
|
||||
_hide_error()
|
||||
|
||||
# Disable button and show loading state
|
||||
login_button.disabled = true
|
||||
login_button.text = "LOGGING IN..."
|
||||
|
||||
# Build request payload
|
||||
var payload := {
|
||||
"email": email,
|
||||
"password": password,
|
||||
"remember_me": remember_checkbox.button_pressed
|
||||
}
|
||||
|
||||
print("[LoginScreen] Attempting login for: %s (remember_me=%s)" % [email, remember_checkbox.button_pressed])
|
||||
|
||||
# Make API request
|
||||
http_client.http_post(
|
||||
"/api/v1/auth/login",
|
||||
payload,
|
||||
_on_login_success,
|
||||
_on_login_error
|
||||
)
|
||||
|
||||
|
||||
## Handle successful login response
|
||||
func _on_login_success(response: APIResponse) -> void:
|
||||
_is_loading = false
|
||||
|
||||
# Re-enable button
|
||||
login_button.disabled = false
|
||||
login_button.text = "LOGIN"
|
||||
|
||||
# Check if response is actually successful
|
||||
if not response.is_success():
|
||||
_on_login_error(response)
|
||||
return
|
||||
|
||||
print("[LoginScreen] Login successful")
|
||||
|
||||
# Extract user data from response
|
||||
# Note: Authentication is cookie-based, so no token in response
|
||||
var result: Dictionary = response.result if response.result is Dictionary else {}
|
||||
var user_data: Dictionary = result.get("user", {})
|
||||
|
||||
if user_data.is_empty():
|
||||
_show_error("Invalid response from server")
|
||||
return
|
||||
|
||||
# Update remember setting
|
||||
state_manager.set_setting("remember_login", remember_checkbox.button_pressed)
|
||||
|
||||
# Save session to StateManager (cookie is already set in HTTPClient)
|
||||
state_manager.set_user_session(user_data)
|
||||
|
||||
print("[LoginScreen] User authenticated: %s" % user_data.get("email", "unknown"))
|
||||
print("[LoginScreen] Session cookie stored, ready for authenticated requests")
|
||||
|
||||
# Navigate back to main screen (which will show authenticated UI)
|
||||
print("[LoginScreen] Navigating to main screen...")
|
||||
get_tree().change_scene_to_file("res://scenes/main.tscn")
|
||||
|
||||
|
||||
## Handle login error
|
||||
func _on_login_error(response: APIResponse) -> void:
|
||||
_is_loading = false
|
||||
|
||||
# Re-enable button
|
||||
login_button.disabled = false
|
||||
login_button.text = "LOGIN"
|
||||
|
||||
# Get error message
|
||||
var error_message := response.get_error_message()
|
||||
|
||||
# Show user-friendly error
|
||||
if error_message.is_empty():
|
||||
error_message = "Login failed. Please try again."
|
||||
|
||||
# Handle specific error cases
|
||||
if response.status == 401:
|
||||
error_message = "Invalid email or password"
|
||||
elif response.status == 0 or response.status >= 500:
|
||||
error_message = "Cannot connect to server. Please check your connection."
|
||||
|
||||
print("[LoginScreen] Login error: %s (status=%d)" % [error_message, response.status])
|
||||
_show_error(error_message)
|
||||
|
||||
|
||||
## Show error message
|
||||
func _show_error(message: String) -> void:
|
||||
error_label.text = message
|
||||
error_label.visible = true
|
||||
|
||||
|
||||
## Hide error message
|
||||
func _hide_error() -> void:
|
||||
error_label.visible = false
|
||||
|
||||
|
||||
## Handle register link press
|
||||
func _on_register_link_pressed() -> void:
|
||||
print("[LoginScreen] Opening registration page in browser")
|
||||
var register_url := Settings.get_web_url() + "/auth/register"
|
||||
var error := OS.shell_open(register_url)
|
||||
|
||||
if error != OK:
|
||||
push_error("[LoginScreen] Failed to open browser: %s" % error)
|
||||
_show_error("Could not open registration page")
|
||||
|
||||
|
||||
## Handle forgot password link press
|
||||
func _on_forgot_password_link_pressed() -> void:
|
||||
print("[LoginScreen] Opening forgot password page in browser")
|
||||
var forgot_url := Settings.get_web_url() + "/auth/forgot-password"
|
||||
var error := OS.shell_open(forgot_url)
|
||||
|
||||
if error != OK:
|
||||
push_error("[LoginScreen] Failed to open browser: %s" % error)
|
||||
_show_error("Could not open password reset page")
|
||||
1
godot_client/scenes/auth/login.gd.uid
Normal file
1
godot_client/scenes/auth/login.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://260dpbodarqy
|
||||
241
godot_client/scenes/auth/login.tscn
Normal file
241
godot_client/scenes/auth/login.tscn
Normal file
@@ -0,0 +1,241 @@
|
||||
[gd_scene load_steps=10 format=3 uid="uid://pon554b5gdnu"]
|
||||
|
||||
[ext_resource type="Theme" uid="uid://bviqieumdiccr" path="res://assets/themes/main_theme.tres" id="1_8wugm"]
|
||||
[ext_resource type="Script" uid="uid://260dpbodarqy" path="res://scenes/auth/login.gd" id="1_lg6fp"]
|
||||
[ext_resource type="Texture2D" uid="uid://cja8kui47qb3d" path="res://assets/ui/main_menu.png" id="3_c4dse"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_card"]
|
||||
bg_color = Color(0, 0, 0, 0.9882353)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.247059, 0.247059, 0.278431, 1)
|
||||
corner_radius_top_left = 8
|
||||
corner_radius_top_right = 8
|
||||
corner_radius_bottom_right = 8
|
||||
corner_radius_bottom_left = 8
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_input_normal"]
|
||||
content_margin_left = 12.0
|
||||
content_margin_top = 8.0
|
||||
content_margin_right = 12.0
|
||||
content_margin_bottom = 8.0
|
||||
bg_color = Color(0.0862745, 0.129412, 0.243137, 1)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.247059, 0.247059, 0.278431, 1)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_input_focus"]
|
||||
content_margin_left = 12.0
|
||||
content_margin_top = 8.0
|
||||
content_margin_right = 12.0
|
||||
content_margin_bottom = 8.0
|
||||
bg_color = Color(0.0862745, 0.129412, 0.243137, 1)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.831373, 0.686275, 0.215686, 1)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_button_normal"]
|
||||
content_margin_left = 16.0
|
||||
content_margin_top = 8.0
|
||||
content_margin_right = 16.0
|
||||
content_margin_bottom = 8.0
|
||||
bg_color = Color(0.831373, 0.686275, 0.215686, 1)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.721569, 0.576471, 0.0392157, 1)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_button_pressed"]
|
||||
content_margin_left = 16.0
|
||||
content_margin_top = 8.0
|
||||
content_margin_right = 16.0
|
||||
content_margin_bottom = 8.0
|
||||
bg_color = Color(0.721569, 0.576471, 0.0392157, 1)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.721569, 0.576471, 0.0392157, 1)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_button_hover"]
|
||||
content_margin_left = 16.0
|
||||
content_margin_top = 8.0
|
||||
content_margin_right = 16.0
|
||||
content_margin_bottom = 8.0
|
||||
bg_color = Color(0.956863, 0.815686, 0.247059, 1)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.721569, 0.576471, 0.0392157, 1)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
|
||||
[node name="Login" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = ExtResource("1_8wugm")
|
||||
script = ExtResource("1_lg6fp")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = ExtResource("3_c4dse")
|
||||
expand_mode = 2
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = ExtResource("1_8wugm")
|
||||
|
||||
[node name="LoginCard" type="PanelContainer" parent="CenterContainer"]
|
||||
custom_minimum_size = Vector2(400, 0)
|
||||
layout_mode = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_card")
|
||||
|
||||
[node name="MainContainer" type="MarginContainer" parent="CenterContainer/LoginCard"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 32
|
||||
theme_override_constants/margin_top = 32
|
||||
theme_override_constants/margin_right = 32
|
||||
theme_override_constants/margin_bottom = 32
|
||||
|
||||
[node name="ContentVBox" type="VBoxContainer" parent="CenterContainer/LoginCard/MainContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 16
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.831373, 0.686275, 0.215686, 1)
|
||||
theme_override_font_sizes/font_size = 24
|
||||
text = "Welcome Back"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="Spacer1" type="Control" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 20)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="EmailLabel" type="Label" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.894118, 0.894118, 0.905882, 1)
|
||||
text = "Email"
|
||||
|
||||
[node name="EmailInput" type="LineEdit" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.894118, 0.894118, 0.905882, 1)
|
||||
theme_override_colors/font_placeholder_color = Color(0.631373, 0.631373, 0.666667, 1)
|
||||
theme_override_colors/caret_color = Color(0.831373, 0.686275, 0.215686, 1)
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_input_normal")
|
||||
theme_override_styles/focus = SubResource("StyleBoxFlat_input_focus")
|
||||
placeholder_text = "Enter your email"
|
||||
|
||||
[node name="Spacer2" type="Control" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 12)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PasswordLabel" type="Label" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.894118, 0.894118, 0.905882, 1)
|
||||
text = "Password"
|
||||
|
||||
[node name="PasswordInput" type="LineEdit" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.894118, 0.894118, 0.905882, 1)
|
||||
theme_override_colors/font_placeholder_color = Color(0.631373, 0.631373, 0.666667, 1)
|
||||
theme_override_colors/caret_color = Color(0.831373, 0.686275, 0.215686, 1)
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_input_normal")
|
||||
theme_override_styles/focus = SubResource("StyleBoxFlat_input_focus")
|
||||
placeholder_text = "Enter your password"
|
||||
secret = true
|
||||
|
||||
[node name="Spacer3" type="Control" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 12)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="RememberCheckBox" type="CheckBox" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.894118, 0.894118, 0.905882, 1)
|
||||
text = "Remember me"
|
||||
|
||||
[node name="Spacer4" type="Control" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 20)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ErrorLabel" type="Label" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(300, 0)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.937255, 0.266667, 0.266667, 1)
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="LoginButton" type="Button" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 44)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.101961, 0.101961, 0.180392, 1)
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_button_normal")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_button_pressed")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_button_hover")
|
||||
text = "LOGIN"
|
||||
|
||||
[node name="Spacer5" type="Control" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 24)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="BottomLinksVBox" type="VBoxContainer" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
alignment = 1
|
||||
|
||||
[node name="RegisterLink" type="Button" parent="CenterContainer/LoginCard/MainContainer/ContentVBox/BottomLinksVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.631373, 0.631373, 0.666667, 1)
|
||||
theme_override_colors/font_hover_color = Color(0.831373, 0.686275, 0.215686, 1)
|
||||
text = "Don't have an account? Register"
|
||||
flat = true
|
||||
|
||||
[node name="ForgotPasswordLink" type="Button" parent="CenterContainer/LoginCard/MainContainer/ContentVBox/BottomLinksVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.631373, 0.631373, 0.666667, 1)
|
||||
theme_override_colors/font_hover_color = Color(0.831373, 0.686275, 0.215686, 1)
|
||||
text = "Forgot password?"
|
||||
flat = true
|
||||
Reference in New Issue
Block a user