extends VBoxContainer const Style := preload("res://scripts/ui/ui_style.gd") const AUTH_LOGIN_URL := "https://pauth.ranaze.com/api/Auth/login" const AUTH_REGISTER_URL := "https://pauth.ranaze.com/api/Auth/register" var main: Control var auth_panel: PanelContainer var login_tab: Button var register_tab: Button var login_fields: VBoxContainer var register_fields: VBoxContainer var username_input: LineEdit var password_input: LineEdit var registration_username_input: LineEdit var registration_email_input: LineEdit var registration_password_input: LineEdit var registration_confirm_input: LineEdit var login_button: Button var register_button: Button var error_label: Label var success_label: Label var login_request: HTTPRequest var register_request: HTTPRequest var auth_mode := "login" var automatic_login_pending := false var login_attempt_username := "" func _ready() -> void: set_anchors_preset(Control.PRESET_FULL_RECT) add_theme_constant_override("separation", 18) var spacer_top := Control.new() spacer_top.size_flags_vertical = Control.SIZE_EXPAND_FILL add_child(spacer_top) var center := CenterContainer.new() center.size_flags_horizontal = Control.SIZE_EXPAND_FILL add_child(center) auth_panel = PanelContainer.new() auth_panel.custom_minimum_size = Vector2(540, 470) auth_panel.add_theme_stylebox_override("panel", Style.panel_box(Color("172337"), Style.CYAN)) center.add_child(auth_panel) var content := VBoxContainer.new() content.add_theme_constant_override("separation", 14) auth_panel.add_child(content) var title := Label.new() title.text = "RanaZe's Doctor Maker" title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER Style.set_label(title, 34, Style.TEXT) content.add_child(title) var subtitle := Label.new() subtitle.text = "Sign in or create an account to start studying." subtitle.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER subtitle.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART Style.set_label(subtitle, 16, Style.MUTED) content.add_child(subtitle) var tabs := HBoxContainer.new() tabs.add_theme_constant_override("separation", 8) content.add_child(tabs) login_tab = Button.new() login_tab.text = "Log In" login_tab.custom_minimum_size = Vector2(0, 44) login_tab.size_flags_horizontal = Control.SIZE_EXPAND_FILL login_tab.pressed.connect(func(): _set_auth_mode("login")) tabs.add_child(login_tab) register_tab = Button.new() register_tab.text = "Create Account" register_tab.custom_minimum_size = Vector2(0, 44) register_tab.size_flags_horizontal = Control.SIZE_EXPAND_FILL register_tab.pressed.connect(func(): _set_auth_mode("register")) tabs.add_child(register_tab) login_fields = VBoxContainer.new() login_fields.add_theme_constant_override("separation", 10) content.add_child(login_fields) username_input = _add_text_field(login_fields, "Username", "enter username") username_input.text_submitted.connect(_on_login_input_submitted) password_input = _add_text_field(login_fields, "Password", "enter password", true) password_input.text_submitted.connect(_on_login_input_submitted) login_button = Button.new() login_button.text = "Log In" login_button.custom_minimum_size = Vector2(0, 50) Style.style_primary_button(login_button) login_button.pressed.connect(_on_login_pressed) login_fields.add_child(login_button) register_fields = VBoxContainer.new() register_fields.add_theme_constant_override("separation", 10) content.add_child(register_fields) registration_username_input = _add_text_field(register_fields, "Username", "3-24 letters, numbers, or underscores") registration_email_input = _add_text_field(register_fields, "Email", "name@example.com") registration_password_input = _add_text_field(register_fields, "Password", "at least 8 characters", true) registration_confirm_input = _add_text_field(register_fields, "Confirm password", "re-enter password", true) registration_confirm_input.text_submitted.connect(_on_register_input_submitted) register_button = Button.new() register_button.text = "Create Account" register_button.custom_minimum_size = Vector2(0, 50) Style.style_primary_button(register_button) register_button.pressed.connect(_on_register_pressed) register_fields.add_child(register_button) success_label = Label.new() success_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER success_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART Style.set_label(success_label, 15, Style.LIME) content.add_child(success_label) error_label = Label.new() error_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER error_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART Style.set_label(error_label, 15, Style.RED) content.add_child(error_label) var spacer_bottom := Control.new() spacer_bottom.size_flags_vertical = Control.SIZE_EXPAND_FILL add_child(spacer_bottom) login_request = HTTPRequest.new() login_request.name = "LoginRequest" login_request.request_completed.connect(_on_login_request_completed) add_child(login_request) register_request = HTTPRequest.new() register_request.name = "RegisterRequest" register_request.request_completed.connect(_on_register_request_completed) add_child(register_request) _set_auth_mode("login") func on_show(_payload: Variant = null) -> void: _set_busy(false) _set_auth_mode("login") _clear_messages() username_input.grab_focus() func _add_text_field(parent: VBoxContainer, field_name: String, placeholder: String, secret: bool = false) -> LineEdit: var label := Label.new() label.text = field_name Style.set_label(label, 15, Style.CYAN) parent.add_child(label) var input := LineEdit.new() input.placeholder_text = placeholder input.secret = secret input.custom_minimum_size = Vector2(0, 42) parent.add_child(input) return input func _set_auth_mode(next_mode: String) -> void: auth_mode = next_mode if login_fields == null: return login_fields.visible = auth_mode == "login" register_fields.visible = auth_mode == "register" auth_panel.custom_minimum_size.y = 470 if auth_mode == "login" else 650 if auth_mode == "login": Style.style_primary_button(login_tab) Style.style_chip(register_tab, Style.CYAN) username_input.grab_focus() else: Style.style_chip(login_tab, Style.CYAN) Style.style_primary_button(register_tab) registration_username_input.grab_focus() _clear_messages() func _on_login_pressed() -> void: _send_login(username_input.text.strip_edges(), password_input.text, false) func _send_login(username: String, password: String, is_automatic: bool) -> void: if username.is_empty() or password.is_empty(): _show_error("Username and password required.") return automatic_login_pending = is_automatic login_attempt_username = username _set_busy(true) login_button.text = "Signing in..." _clear_messages() if is_automatic: success_label.text = "Account created. Signing you in..." var payload := {"username": username, "password": password} var headers := PackedStringArray(["Content-Type: application/json"]) var request_error := login_request.request(AUTH_LOGIN_URL, headers, HTTPClient.METHOD_POST, JSON.stringify(payload)) if request_error != OK: _set_busy(false) login_button.text = "Log In" _show_error("Failed to send login request.") func _on_login_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void: _set_busy(false) login_button.text = "Log In" if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300: if automatic_login_pending: _set_auth_mode("login") username_input.text = login_attempt_username password_input.text = "" _show_error("Account created, but automatic login failed. Please log in with your new account.") else: _show_error("Login failed. Check your credentials and try again.") automatic_login_pending = false return var response: Variant = JSON.parse_string(body.get_string_from_utf8()) if typeof(response) != TYPE_DICTIONARY: _show_error("Login response was not valid JSON.") return var token := String(response.get("accessToken", "")) var response_username := String(response.get("username", login_attempt_username)) if token.is_empty(): _show_error("Login response did not include an access token.") return AuthState.set_session( response_username, token, String(response.get("refreshToken", "")), String(response.get("role", "")), String(response.get("exp", "")) ) QuestionBankService.load_questions_from_api() password_input.text = "" registration_password_input.text = "" registration_confirm_input.text = "" automatic_login_pending = false main.show_screen("selection", "exam") func _on_register_pressed() -> void: var username := registration_username_input.text.strip_edges() var email := registration_email_input.text.strip_edges() var password := registration_password_input.text var confirmation := registration_confirm_input.text var validation_error := _validate_registration(username, email, password, confirmation) if validation_error != "": _show_error(validation_error) return _set_busy(true) register_button.text = "Creating account..." _clear_messages() var payload := {"username": username, "email": email, "password": password} var headers := PackedStringArray(["Content-Type: application/json"]) var request_error := register_request.request(AUTH_REGISTER_URL, headers, HTTPClient.METHOD_POST, JSON.stringify(payload)) if request_error != OK: _set_busy(false) register_button.text = "Create Account" _show_error("Failed to send account creation request.") func _on_register_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void: _set_busy(false) register_button.text = "Create Account" if result != HTTPRequest.RESULT_SUCCESS: _clear_registration_passwords() _show_error("Network error. Please try again.") return if response_code < 200 or response_code >= 300: _clear_registration_passwords() var server_message := _response_message(body) _show_error(server_message if server_message != "" else "Account creation failed.") return var username := registration_username_input.text.strip_edges() var password := registration_password_input.text username_input.text = username _send_login(username, password, true) func _validate_registration(username: String, email: String, password: String, confirmation: String) -> String: var username_pattern := RegEx.new() username_pattern.compile("^[A-Za-z0-9_]{3,24}$") if username_pattern.search(username) == null: registration_username_input.grab_focus() return "Username must be 3-24 characters using only letters, numbers, or underscores." var email_pattern := RegEx.new() email_pattern.compile("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$") if email_pattern.search(email) == null: registration_email_input.grab_focus() return "Enter a valid email address." if password.length() < 8: registration_password_input.grab_focus() return "Password must be at least 8 characters." if password != confirmation: registration_confirm_input.grab_focus() return "Passwords do not match." return "" func _set_busy(is_busy: bool) -> void: if login_button == null: return login_button.disabled = is_busy register_button.disabled = is_busy login_tab.disabled = is_busy register_tab.disabled = is_busy username_input.editable = not is_busy password_input.editable = not is_busy registration_username_input.editable = not is_busy registration_email_input.editable = not is_busy registration_password_input.editable = not is_busy registration_confirm_input.editable = not is_busy func _clear_registration_passwords() -> void: registration_password_input.text = "" registration_confirm_input.text = "" func _response_message(body: PackedByteArray) -> String: var raw := body.get_string_from_utf8().strip_edges() var parsed: Variant = JSON.parse_string(raw) return String(parsed) if typeof(parsed) == TYPE_STRING else raw func _on_login_input_submitted(_new_text: String) -> void: _on_login_pressed() func _on_register_input_submitted(_new_text: String) -> void: _on_register_pressed() func _clear_messages() -> void: error_label.text = "" success_label.text = "" func _show_error(message: String) -> void: success_label.text = "" error_label.text = message