Add legal consent and reliable external redirects
This commit is contained in:
@@ -22,6 +22,11 @@ var expires_at := ""
|
||||
var expiration_unix := 0.0
|
||||
var refresh_in_progress := false
|
||||
var warning_active := false
|
||||
var requires_legal_acceptance := false
|
||||
var terms_version := ""
|
||||
var privacy_version := ""
|
||||
var terms_url := ""
|
||||
var privacy_url := ""
|
||||
var auth_base_url := DEFAULT_AUTH_BASE_URL
|
||||
var expiry_timer: Timer
|
||||
var refresh_request: HTTPRequest
|
||||
@@ -42,7 +47,7 @@ func _ready() -> void:
|
||||
refresh_request.request_completed.connect(_on_refresh_completed)
|
||||
add_child(refresh_request)
|
||||
|
||||
func set_session(new_username: String, token: String, new_refresh_token: String = "", new_role: String = "", token_expiration: String = "") -> void:
|
||||
func set_session(new_username: String, token: String, new_refresh_token: String = "", new_role: String = "", token_expiration: String = "", legal: Dictionary = {}) -> void:
|
||||
session_revision += 1
|
||||
is_logged_in = not token.is_empty()
|
||||
username = new_username
|
||||
@@ -54,6 +59,7 @@ func set_session(new_username: String, token: String, new_refresh_token: String
|
||||
refresh_in_progress = false
|
||||
refresh_revision = -1
|
||||
warning_active = false
|
||||
set_legal_state(legal, false)
|
||||
if is_logged_in and expiration_unix > 0.0:
|
||||
expiry_timer.start()
|
||||
else:
|
||||
@@ -63,6 +69,16 @@ func set_session(new_username: String, token: String, new_refresh_token: String
|
||||
_evaluate_expiry.call_deferred()
|
||||
|
||||
|
||||
func set_legal_state(legal: Dictionary, notify := true) -> void:
|
||||
requires_legal_acceptance = bool(legal.get("requiresLegalAcceptance", false))
|
||||
terms_version = str(legal.get("termsVersion", terms_version))
|
||||
privacy_version = str(legal.get("privacyVersion", privacy_version))
|
||||
terms_url = str(legal.get("termsUrl", terms_url))
|
||||
privacy_url = str(legal.get("privacyUrl", privacy_url))
|
||||
if notify:
|
||||
auth_changed.emit()
|
||||
|
||||
|
||||
func clear_session() -> void:
|
||||
_clear_session_fields()
|
||||
auth_changed.emit()
|
||||
@@ -175,6 +191,9 @@ func _on_refresh_completed(result: int, response_code: int, _headers: PackedStri
|
||||
expires_at = new_expiration
|
||||
expiration_unix = new_expiration_unix
|
||||
warning_active = false
|
||||
var legal_value: Variant = parsed.get("legal", {})
|
||||
if typeof(legal_value) == TYPE_DICTIONARY:
|
||||
set_legal_state(legal_value, false)
|
||||
session_revision += 1
|
||||
expiry_timer.start()
|
||||
auth_changed.emit()
|
||||
@@ -197,6 +216,11 @@ func _clear_session_fields() -> void:
|
||||
refresh_in_progress = false
|
||||
refresh_revision = -1
|
||||
warning_active = false
|
||||
requires_legal_acceptance = false
|
||||
terms_version = ""
|
||||
privacy_version = ""
|
||||
terms_url = ""
|
||||
privacy_url = ""
|
||||
|
||||
|
||||
func _parse_expiration(value: String, token: String = "") -> float:
|
||||
|
||||
@@ -5,7 +5,7 @@ const DEFAULT_URL := "https://s3q.ranaze.com/api/donations"
|
||||
const REQUEST_TIMEOUT_SECONDS := 30.0
|
||||
const POLL_INTERVAL_SECONDS := 3.0
|
||||
const POLL_LIMIT := 200
|
||||
const CHECKOUT_STORAGE_KEY := "medmindDonationCheckoutUrl"
|
||||
const CHECKOUT_STORAGE_KEY := "ranaze_donation_checkout_url"
|
||||
|
||||
signal checkout_started(donation_id: String, checkout_url: String)
|
||||
signal donation_status_changed(status: String, donation: Dictionary)
|
||||
@@ -140,14 +140,14 @@ func _on_status_completed(result: int, response_code: int, _headers: PackedStrin
|
||||
func _prepare_browser_window() -> void:
|
||||
if OS.has_feature("web"):
|
||||
JavaScriptBridge.eval(
|
||||
"window.localStorage.removeItem('%s'); window.medmindDonationWindow = window.open('about:blank', 'medmind_donation_checkout'); if (window.medmindDonationWindow) { var medmindWaitingWindow = window.medmindDonationWindow; medmindWaitingWindow.document.title = 'RanaZe secure checkout'; medmindWaitingWindow.document.body.innerHTML = '<main id=\"medmind-checkout\" style=\"font:16px system-ui;padding:48px;color:#172033\"><strong>Opening secure checkout...</strong><p>This window will continue to Stripe.</p></main>'; medmindWaitingWindow.setInterval(function () { var checkoutUrl = medmindWaitingWindow.localStorage.getItem('%s'); if (checkoutUrl) { var checkout = medmindWaitingWindow.document.getElementById('medmind-checkout'); checkout.innerHTML = '<strong>Secure checkout is ready.</strong><p><a id=\"medmind-checkout-link\" style=\"color:#2563eb;font-weight:600\">Continue to Stripe</a></p>'; medmindWaitingWindow.document.getElementById('medmind-checkout-link').href = checkoutUrl; medmindWaitingWindow.localStorage.removeItem('%s'); medmindWaitingWindow.location.replace(checkoutUrl); } }, 150); }" % [CHECKOUT_STORAGE_KEY, CHECKOUT_STORAGE_KEY, CHECKOUT_STORAGE_KEY],
|
||||
"window.localStorage.removeItem('%s'); var ranazeDonationBridge = new URL('/external-redirect.html?key=%s&provider=Stripe', window.location.origin).href; window.medmindDonationWindow = window.open(ranazeDonationBridge, 'medmind_donation_checkout');" % [CHECKOUT_STORAGE_KEY, CHECKOUT_STORAGE_KEY],
|
||||
true)
|
||||
|
||||
|
||||
func _open_checkout_url(url: String) -> void:
|
||||
if OS.has_feature("web"):
|
||||
JavaScriptBridge.eval(
|
||||
"var medmindCheckoutUrl = %s; window.localStorage.setItem('%s', medmindCheckoutUrl); if (window.medmindDonationWindow && !window.medmindDonationWindow.closed) { window.medmindDonationWindow.focus(); } else { var medmindCheckoutWindow = window.open(medmindCheckoutUrl, 'medmind_donation_checkout'); if (medmindCheckoutWindow) { window.medmindDonationWindow = medmindCheckoutWindow; medmindCheckoutWindow.focus(); } else { window.location.assign(medmindCheckoutUrl); } }" % [JSON.stringify(url), CHECKOUT_STORAGE_KEY],
|
||||
"var ranazeDonationUrl = %s; window.localStorage.setItem('%s', ranazeDonationUrl); if (window.medmindDonationWindow && !window.medmindDonationWindow.closed) { try { window.medmindDonationWindow.location.replace(ranazeDonationUrl); } catch (_) {} window.medmindDonationWindow.focus(); } else { var ranazeDonationPopup = window.open(ranazeDonationUrl, 'medmind_donation_checkout'); if (ranazeDonationPopup) { window.medmindDonationWindow = ranazeDonationPopup; ranazeDonationPopup.focus(); } else { window.location.assign(ranazeDonationUrl); } }" % [JSON.stringify(url), CHECKOUT_STORAGE_KEY],
|
||||
true)
|
||||
else:
|
||||
OS.shell_open(url)
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
extends Node
|
||||
|
||||
signal acceptance_succeeded
|
||||
signal acceptance_failed(message: String)
|
||||
|
||||
const EndpointConfig := preload("res://scripts/autoload/api_endpoint_config.gd")
|
||||
const DEFAULT_AUTH_BASE_URL := "https://pauth.ranaze.com/api/Auth"
|
||||
const DOCUMENT_PATH := "res://legal/legal_documents.json"
|
||||
|
||||
var documents: Dictionary = {}
|
||||
var auth_base_url := DEFAULT_AUTH_BASE_URL
|
||||
var request: HTTPRequest
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
auth_base_url = EndpointConfig.resolve(DEFAULT_AUTH_BASE_URL, "/api/Auth")
|
||||
_load_documents()
|
||||
request = HTTPRequest.new()
|
||||
request.timeout = 20.0
|
||||
request.request_completed.connect(_on_accept_completed)
|
||||
add_child(request)
|
||||
|
||||
|
||||
func get_document(kind: String) -> Dictionary:
|
||||
var value: Variant = documents.get(kind, {})
|
||||
return value if typeof(value) == TYPE_DICTIONARY else {}
|
||||
|
||||
|
||||
func public_url(kind: String) -> String:
|
||||
if kind == "terms" and not AuthState.terms_url.is_empty():
|
||||
return AuthState.terms_url
|
||||
if kind == "privacy" and not AuthState.privacy_url.is_empty():
|
||||
return AuthState.privacy_url
|
||||
return "https://doctor.ranaze.com/%s.html" % kind
|
||||
|
||||
|
||||
func accept_current() -> void:
|
||||
if request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:
|
||||
return
|
||||
if not AuthState.can_send_authenticated_request():
|
||||
acceptance_failed.emit("Your session expired. Sign in again.")
|
||||
return
|
||||
var headers := PackedStringArray([
|
||||
"Content-Type: application/json",
|
||||
"Authorization: Bearer %s" % AuthState.access_token
|
||||
])
|
||||
var error := request.request(
|
||||
"%s/legal/accept" % auth_base_url.rstrip("/"),
|
||||
headers,
|
||||
HTTPClient.METHOD_POST,
|
||||
JSON.stringify({"accept": true}))
|
||||
if error != OK:
|
||||
acceptance_failed.emit("Unable to contact the authentication service. Try again.")
|
||||
|
||||
|
||||
func _load_documents() -> void:
|
||||
var file := FileAccess.open(DOCUMENT_PATH, FileAccess.READ)
|
||||
if file == null:
|
||||
push_error("Unable to load legal documents")
|
||||
return
|
||||
var parsed: Variant = JSON.parse_string(file.get_as_text())
|
||||
if typeof(parsed) == TYPE_DICTIONARY:
|
||||
documents = parsed
|
||||
|
||||
|
||||
func _on_accept_completed(result: int, code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||
if result != HTTPRequest.RESULT_SUCCESS or code < 200 or code >= 300:
|
||||
acceptance_failed.emit("Unable to save your agreement. Check your connection and try again.")
|
||||
return
|
||||
var parsed: Variant = JSON.parse_string(body.get_string_from_utf8())
|
||||
if typeof(parsed) != TYPE_DICTIONARY:
|
||||
acceptance_failed.emit("The authentication service returned an invalid response.")
|
||||
return
|
||||
AuthState.set_legal_state(parsed)
|
||||
acceptance_succeeded.emit()
|
||||
@@ -0,0 +1 @@
|
||||
uid://dp43xyka8queb
|
||||
@@ -159,6 +159,15 @@ func _build_ui() -> void:
|
||||
var disclosure := _body("Contributions are voluntary, one-time, and not tax-deductible. Card details are entered only on Stripe's secure checkout page.")
|
||||
disclosure.add_theme_font_size_override("font_size", 14)
|
||||
contribution.add_child(disclosure)
|
||||
var legal_links := HBoxContainer.new()
|
||||
legal_links.add_theme_constant_override("separation", 12)
|
||||
contribution.add_child(legal_links)
|
||||
for spec in [["Terms of Service", "terms"], ["Privacy Policy", "privacy"]]:
|
||||
var link := Button.new()
|
||||
link.text = spec[0]
|
||||
link.flat = true
|
||||
link.pressed.connect(_open_legal_document.bind(str(spec[1])))
|
||||
legal_links.add_child(link)
|
||||
|
||||
|
||||
func on_show(_payload: Variant = null) -> void:
|
||||
@@ -173,6 +182,10 @@ func on_hide() -> void:
|
||||
donation_service.stop_polling()
|
||||
|
||||
|
||||
func _open_legal_document(kind: String) -> void:
|
||||
main.open_legal_document(kind)
|
||||
|
||||
|
||||
static func parse_amount_cents(value: String) -> int:
|
||||
return DonationAmount.parse_cents(value)
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
extends VBoxContainer
|
||||
|
||||
const Brand := preload("res://scripts/ui/medmind_style.gd")
|
||||
const Builder := preload("res://scripts/ui/legal_document_builder.gd")
|
||||
|
||||
var main: Control
|
||||
var document_content: VBoxContainer
|
||||
var consent: CheckBox
|
||||
var accept_button: Button
|
||||
var status: Label
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
add_theme_constant_override("separation", 14)
|
||||
var tabs := HBoxContainer.new()
|
||||
add_child(tabs)
|
||||
for kind in ["terms", "privacy"]:
|
||||
var button := Button.new()
|
||||
button.text = "Terms of Service" if kind == "terms" else "Privacy Policy"
|
||||
button.pressed.connect(_show_document.bind(kind))
|
||||
tabs.add_child(button)
|
||||
var scroll := ScrollContainer.new()
|
||||
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
add_child(scroll)
|
||||
document_content = VBoxContainer.new()
|
||||
document_content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
document_content.add_theme_constant_override("separation", 14)
|
||||
scroll.add_child(document_content)
|
||||
consent = CheckBox.new()
|
||||
consent.text = "I agree to the Terms of Service and acknowledge the Privacy Policy."
|
||||
consent.toggled.connect(func(enabled: bool): accept_button.disabled = not enabled)
|
||||
add_child(consent)
|
||||
status = Label.new()
|
||||
status.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
add_child(status)
|
||||
var actions := HBoxContainer.new()
|
||||
actions.alignment = BoxContainer.ALIGNMENT_END
|
||||
add_child(actions)
|
||||
var logout := Button.new()
|
||||
logout.text = "Log out"
|
||||
logout.pressed.connect(_logout)
|
||||
actions.add_child(logout)
|
||||
accept_button = Button.new()
|
||||
accept_button.text = "Agree and continue"
|
||||
accept_button.disabled = true
|
||||
accept_button.pressed.connect(_accept)
|
||||
actions.add_child(accept_button)
|
||||
LegalService.acceptance_succeeded.connect(_on_accepted)
|
||||
LegalService.acceptance_failed.connect(_on_failed)
|
||||
_show_document("terms")
|
||||
|
||||
|
||||
func on_show(_payload: Variant = null) -> void:
|
||||
consent.set_pressed_no_signal(false)
|
||||
accept_button.disabled = true
|
||||
status.text = ""
|
||||
|
||||
|
||||
func _show_document(kind: String) -> void:
|
||||
Builder.populate(document_content, LegalService.get_document(kind))
|
||||
|
||||
|
||||
func _accept() -> void:
|
||||
accept_button.disabled = true
|
||||
status.text = "Saving your agreement..."
|
||||
LegalService.accept_current()
|
||||
|
||||
|
||||
func _on_accepted() -> void:
|
||||
status.text = ""
|
||||
main.show_screen("dashboard")
|
||||
|
||||
|
||||
func _on_failed(message: String) -> void:
|
||||
status.text = message
|
||||
accept_button.disabled = not consent.button_pressed
|
||||
|
||||
|
||||
func _logout() -> void:
|
||||
AuthState.clear_session()
|
||||
main.show_screen("login")
|
||||
@@ -0,0 +1 @@
|
||||
uid://u0ydfjugfyua
|
||||
@@ -0,0 +1,44 @@
|
||||
class_name LegalDocumentBuilder
|
||||
extends RefCounted
|
||||
|
||||
const Brand := preload("res://scripts/ui/medmind_style.gd")
|
||||
|
||||
|
||||
static func populate(container: VBoxContainer, document: Dictionary) -> void:
|
||||
for child in container.get_children():
|
||||
child.queue_free()
|
||||
var title := Label.new()
|
||||
title.text = str(document.get("title", "Legal document"))
|
||||
title.add_theme_font_override("font", Brand.font)
|
||||
title.add_theme_font_size_override("font_size", 30)
|
||||
container.add_child(title)
|
||||
var metadata := Label.new()
|
||||
metadata.text = "Effective %s | Version %s" % [document.get("effectiveDate", ""), document.get("version", "")]
|
||||
metadata.add_theme_font_override("font", Brand.font)
|
||||
metadata.add_theme_font_size_override("font_size", 14)
|
||||
metadata.add_theme_color_override("font_color", Brand.muted_color(UIThemeService.is_dark_mode()))
|
||||
container.add_child(metadata)
|
||||
var summary := _paragraph(str(document.get("summary", "")))
|
||||
container.add_child(summary)
|
||||
for section_value in document.get("sections", []):
|
||||
if typeof(section_value) != TYPE_DICTIONARY:
|
||||
continue
|
||||
var section: Dictionary = section_value
|
||||
var heading := Label.new()
|
||||
heading.text = str(section.get("heading", ""))
|
||||
heading.add_theme_font_override("font", Brand.font)
|
||||
heading.add_theme_font_size_override("font_size", 20)
|
||||
container.add_child(heading)
|
||||
for paragraph_value in section.get("paragraphs", []):
|
||||
container.add_child(_paragraph(str(paragraph_value)))
|
||||
|
||||
|
||||
static func _paragraph(value: String) -> Label:
|
||||
var label := Label.new()
|
||||
label.text = value
|
||||
label.add_theme_font_override("font", Brand.font)
|
||||
label.add_theme_font_size_override("font_size", 16)
|
||||
label.add_theme_color_override("font_color", Brand.muted_color(UIThemeService.is_dark_mode()))
|
||||
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
return label
|
||||
@@ -0,0 +1 @@
|
||||
uid://b0dukpxdyw2x4
|
||||
@@ -0,0 +1,53 @@
|
||||
extends Control
|
||||
|
||||
const Brand := preload("res://scripts/ui/medmind_style.gd")
|
||||
const Builder := preload("res://scripts/ui/legal_document_builder.gd")
|
||||
|
||||
var content: VBoxContainer
|
||||
var current_kind := "terms"
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
visible = false
|
||||
top_level = true
|
||||
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
var shade := ColorRect.new()
|
||||
shade.color = Color(0, 0, 0, 0.72)
|
||||
shade.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
add_child(shade)
|
||||
var center := CenterContainer.new()
|
||||
center.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
add_child(center)
|
||||
var panel := PanelContainer.new()
|
||||
panel.custom_minimum_size = Vector2(720, 620)
|
||||
panel.add_theme_stylebox_override("panel", Brand.panel_box(UIThemeService.is_dark_mode()))
|
||||
center.add_child(panel)
|
||||
var layout := VBoxContainer.new()
|
||||
layout.add_theme_constant_override("separation", 14)
|
||||
panel.add_child(layout)
|
||||
var scroll := ScrollContainer.new()
|
||||
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
layout.add_child(scroll)
|
||||
content = VBoxContainer.new()
|
||||
content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
content.add_theme_constant_override("separation", 14)
|
||||
scroll.add_child(content)
|
||||
var actions := HBoxContainer.new()
|
||||
actions.alignment = BoxContainer.ALIGNMENT_END
|
||||
layout.add_child(actions)
|
||||
var browser := Button.new()
|
||||
browser.text = "Open in browser"
|
||||
browser.pressed.connect(func(): OS.shell_open(LegalService.public_url(current_kind)))
|
||||
actions.add_child(browser)
|
||||
var close := Button.new()
|
||||
close.text = "Close"
|
||||
close.pressed.connect(hide)
|
||||
actions.add_child(close)
|
||||
|
||||
|
||||
func show_document(kind: String) -> void:
|
||||
current_kind = kind
|
||||
Builder.populate(content, LegalService.get_document(kind))
|
||||
visible = true
|
||||
@@ -0,0 +1 @@
|
||||
uid://bkjd11e1kjp21
|
||||
@@ -27,6 +27,7 @@ var registration_username_input: LineEdit
|
||||
var registration_email_input: LineEdit
|
||||
var registration_password_input: LineEdit
|
||||
var registration_confirm_input: LineEdit
|
||||
var registration_legal_check: CheckBox
|
||||
var reset_login_input: LineEdit
|
||||
var reset_code_input: LineEdit
|
||||
var reset_password_input: LineEdit
|
||||
@@ -36,6 +37,8 @@ var steam_action: OptionButton
|
||||
var steam_username_input: LineEdit
|
||||
var steam_email_input: LineEdit
|
||||
var steam_password_input: LineEdit
|
||||
var steam_legal_check: CheckBox
|
||||
var panel_gutter: MarginContainer
|
||||
var submit_buttons: Array[Button] = []
|
||||
var fields: Array[LineEdit] = []
|
||||
var requests: Dictionary = {}
|
||||
@@ -182,10 +185,14 @@ func _build_ui() -> void:
|
||||
var scroll := ScrollContainer.new()
|
||||
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
auth_panel.add_child(scroll)
|
||||
panel_gutter = MarginContainer.new()
|
||||
panel_gutter.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
panel_gutter.add_theme_constant_override("margin_right", 28)
|
||||
scroll.add_child(panel_gutter)
|
||||
panel_content = VBoxContainer.new()
|
||||
panel_content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
panel_content.add_theme_constant_override("separation", 24)
|
||||
scroll.add_child(panel_content)
|
||||
panel_gutter.add_child(panel_content)
|
||||
|
||||
var panel_header := HBoxContainer.new()
|
||||
panel_header.add_theme_constant_override("separation", 18)
|
||||
@@ -221,6 +228,11 @@ func _build_ui() -> void:
|
||||
message_label.add_theme_font_size_override("font_size", 14)
|
||||
message_label.add_theme_font_override("font", Brand.font)
|
||||
panel_content.add_child(message_label)
|
||||
var legal_footer := HBoxContainer.new()
|
||||
legal_footer.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
legal_footer.add_theme_constant_override("separation", 12)
|
||||
panel_content.add_child(legal_footer)
|
||||
_add_legal_links(legal_footer)
|
||||
|
||||
|
||||
func _build_login_view() -> VBoxContainer:
|
||||
@@ -259,6 +271,7 @@ func _build_register_view() -> VBoxContainer:
|
||||
registration_password_input = _add_field(view, "Password", "At least 8 characters", true)
|
||||
registration_confirm_input = _add_field(view, "Confirm password", "Re-enter your password", true)
|
||||
_add_show_password(view, [registration_password_input, registration_confirm_input])
|
||||
registration_legal_check = _add_legal_consent(view)
|
||||
_add_primary_button(view, "Create Account", _on_register_pressed)
|
||||
_add_back_button(view, "Already have an account? Sign in", "login")
|
||||
registration_confirm_input.text_submitted.connect(func(_text: String): _on_register_pressed())
|
||||
@@ -297,11 +310,37 @@ func _build_steam_view() -> VBoxContainer:
|
||||
steam_email_input = _add_field(view, "Email", "[email protected]")
|
||||
steam_password_input = _add_field(view, "Password", "At least 8 characters", true)
|
||||
_add_show_password(view, [steam_password_input])
|
||||
steam_legal_check = _add_legal_consent(view)
|
||||
_add_primary_button(view, "Continue", _complete_steam)
|
||||
_add_back_button(view, "Cancel Steam sign in", "login")
|
||||
return view
|
||||
|
||||
|
||||
func _add_legal_consent(parent: VBoxContainer) -> CheckBox:
|
||||
var check := CheckBox.new()
|
||||
check.text = "I agree to the Terms of Service and acknowledge the Privacy Policy."
|
||||
check.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
parent.add_child(check)
|
||||
var links := HBoxContainer.new()
|
||||
links.add_theme_constant_override("separation", 12)
|
||||
parent.add_child(links)
|
||||
_add_legal_links(links)
|
||||
return check
|
||||
|
||||
|
||||
func _add_legal_links(parent: HBoxContainer) -> void:
|
||||
for spec in [["Terms of Service", "terms"], ["Privacy Policy", "privacy"]]:
|
||||
var link := Button.new()
|
||||
link.text = spec[0]
|
||||
link.flat = true
|
||||
link.pressed.connect(_open_legal_document.bind(str(spec[1])))
|
||||
parent.add_child(link)
|
||||
|
||||
|
||||
func _open_legal_document(kind: String) -> void:
|
||||
main.open_legal_document(kind)
|
||||
|
||||
|
||||
func _new_form() -> VBoxContainer:
|
||||
var form := VBoxContainer.new()
|
||||
form.add_theme_constant_override("separation", 16)
|
||||
@@ -446,12 +485,14 @@ func _update_responsive_layout() -> void:
|
||||
theme_button.position = Vector2(-190, 20)
|
||||
theme_button.add_theme_font_size_override("font_size", 64)
|
||||
auth_panel.custom_minimum_size = Vector2(1700, 2300)
|
||||
panel_gutter.add_theme_constant_override("margin_right", 96)
|
||||
_apply_compact_scale(panel_content, 3.8)
|
||||
else:
|
||||
theme_button.custom_minimum_size = Vector2(44, 44)
|
||||
theme_button.position = Vector2(-58, 14)
|
||||
theme_button.add_theme_font_size_override("font_size", 20)
|
||||
auth_panel.custom_minimum_size = Vector2(640, 790) if compact_desktop else Vector2(710, 820)
|
||||
panel_gutter.add_theme_constant_override("margin_right", 28)
|
||||
_apply_compact_scale(panel_content, 1.0)
|
||||
_apply_theme()
|
||||
layout_updating = false
|
||||
@@ -559,8 +600,10 @@ func _on_register_pressed() -> void:
|
||||
var password := registration_password_input.text
|
||||
var error := _validate_account(username, email, password, registration_confirm_input.text)
|
||||
if not error.is_empty(): return _show_error(error)
|
||||
if not registration_legal_check.button_pressed:
|
||||
return _show_error("Agree to the Terms of Service and acknowledge the Privacy Policy to create an account.")
|
||||
login_attempt_username = username
|
||||
_send_json("register", "/register", {"username": username, "email": email, "password": password}, "Creating account...")
|
||||
_send_json("register", "/register", {"username": username, "email": email, "password": password, "acceptLegal": true}, "Creating account...")
|
||||
|
||||
|
||||
func _on_register_completed(result: int, code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||
@@ -608,7 +651,7 @@ func _on_reset_confirmed(result: int, code: int, _headers: PackedStringArray, bo
|
||||
func _start_steam() -> void:
|
||||
if OS.has_feature("web"):
|
||||
var popup_result: Variant = JavaScriptBridge.eval(
|
||||
"window.localStorage.removeItem('%s'); window.ranazeSteamWindow = window.open('about:blank', 'ranaze_steam'); if (window.ranazeSteamWindow) { var ranazeSteamWaitingWindow = window.ranazeSteamWindow; ranazeSteamWaitingWindow.document.title = 'RanaZe Steam sign in'; ranazeSteamWaitingWindow.document.body.innerHTML = '<main id=\"ranaze-steam-login\" style=\"font:16px system-ui;padding:48px;color:#172033\"><strong>Opening Steam sign in...</strong><p>This window will continue to Steam.</p></main>'; ranazeSteamWaitingWindow.setInterval(function () { var steamAuthUrl = ranazeSteamWaitingWindow.localStorage.getItem('%s'); if (steamAuthUrl) { var steamLogin = ranazeSteamWaitingWindow.document.getElementById('ranaze-steam-login'); steamLogin.innerHTML = '<strong>Steam sign in is ready.</strong><p><a id=\"ranaze-steam-link\" style=\"color:#2563eb;font-weight:600\">Continue to Steam</a></p>'; ranazeSteamWaitingWindow.document.getElementById('ranaze-steam-link').href = steamAuthUrl; ranazeSteamWaitingWindow.localStorage.removeItem('%s'); ranazeSteamWaitingWindow.location.replace(steamAuthUrl); } }, 150); } window.ranazeSteamWindow !== null;" % [STEAM_AUTH_STORAGE_KEY, STEAM_AUTH_STORAGE_KEY, STEAM_AUTH_STORAGE_KEY],
|
||||
"window.localStorage.removeItem('%s'); var ranazeSteamBridge = new URL('/external-redirect.html?key=%s&provider=Steam', window.location.origin).href; window.ranazeSteamWindow = window.open(ranazeSteamBridge, 'ranaze_steam'); window.ranazeSteamWindow !== null;" % [STEAM_AUTH_STORAGE_KEY, STEAM_AUTH_STORAGE_KEY],
|
||||
true
|
||||
)
|
||||
steam_popup_opened = bool(popup_result)
|
||||
@@ -630,7 +673,7 @@ func _on_steam_started(result: int, code: int, _headers: PackedStringArray, body
|
||||
return _show_error("Steam returned an invalid sign-in response.")
|
||||
if OS.has_feature("web"):
|
||||
JavaScriptBridge.eval(
|
||||
"var ranazeSteamAuthUrl = %s; window.localStorage.setItem('%s', ranazeSteamAuthUrl); if (window.ranazeSteamWindow && !window.ranazeSteamWindow.closed) { window.ranazeSteamWindow.focus(); } else { var ranazeSteamPopup = window.open(ranazeSteamAuthUrl, 'ranaze_steam'); if (ranazeSteamPopup) { window.ranazeSteamWindow = ranazeSteamPopup; ranazeSteamPopup.focus(); } else { window.location.assign(ranazeSteamAuthUrl); } }" % [JSON.stringify(auth_url), STEAM_AUTH_STORAGE_KEY],
|
||||
"var ranazeSteamAuthUrl = %s; window.localStorage.setItem('%s', ranazeSteamAuthUrl); if (window.ranazeSteamWindow && !window.ranazeSteamWindow.closed) { try { window.ranazeSteamWindow.location.replace(ranazeSteamAuthUrl); } catch (_) {} window.ranazeSteamWindow.focus(); } else { var ranazeSteamPopup = window.open(ranazeSteamAuthUrl, 'ranaze_steam'); if (ranazeSteamPopup) { window.ranazeSteamWindow = ranazeSteamPopup; ranazeSteamPopup.focus(); } else { window.location.assign(ranazeSteamAuthUrl); } }" % [JSON.stringify(auth_url), STEAM_AUTH_STORAGE_KEY],
|
||||
true
|
||||
)
|
||||
else:
|
||||
@@ -674,6 +717,7 @@ func _on_steam_status(result: int, code: int, _headers: PackedStringArray, body:
|
||||
|
||||
func _on_steam_action_changed(index: int) -> void:
|
||||
steam_email_input.visible = index == 0
|
||||
steam_legal_check.visible = index == 0
|
||||
steam_username_input.placeholder_text = "Choose a username" if index == 0 else "Existing email or username"
|
||||
|
||||
|
||||
@@ -682,12 +726,14 @@ func _complete_steam() -> void:
|
||||
if action == "create":
|
||||
var validation := _validate_account(steam_username_input.text.strip_edges(), steam_email_input.text.strip_edges(), steam_password_input.text, steam_password_input.text)
|
||||
if not validation.is_empty(): return _show_error(validation)
|
||||
if not steam_legal_check.button_pressed:
|
||||
return _show_error("Agree to the Terms of Service and acknowledge the Privacy Policy to create an account.")
|
||||
elif steam_username_input.text.strip_edges().is_empty() or steam_password_input.text.is_empty():
|
||||
return _show_error("Enter your existing account credentials.")
|
||||
_send_json("steam_complete", "/steam/complete", {
|
||||
"transactionId": steam_transaction_id, "action": action,
|
||||
"username": steam_username_input.text.strip_edges(), "email": steam_email_input.text.strip_edges(),
|
||||
"password": steam_password_input.text
|
||||
"password": steam_password_input.text, "acceptLegal": action == "create"
|
||||
}, "Connecting account...")
|
||||
|
||||
|
||||
@@ -718,12 +764,14 @@ func _accept_session(body: PackedByteArray, fallback_username: String) -> void:
|
||||
var response := _parse_dictionary(body)
|
||||
var token := str(response.get("accessToken", ""))
|
||||
if token.is_empty(): return _show_error("Sign in response did not include an access token.")
|
||||
var legal_value: Variant = response.get("legal", {})
|
||||
var legal: Dictionary = legal_value if typeof(legal_value) == TYPE_DICTIONARY else {}
|
||||
AuthState.set_session(str(response.get("username", fallback_username)), token,
|
||||
str(response.get("refreshToken", "")), str(response.get("role", "")), str(response.get("exp", "")))
|
||||
str(response.get("refreshToken", "")), str(response.get("role", "")), str(response.get("exp", "")), legal)
|
||||
password_input.text = ""
|
||||
_set_busy(false)
|
||||
UISoundService.play_sound("correct")
|
||||
main.show_screen("dashboard")
|
||||
main.show_screen("legal_consent" if AuthState.requires_legal_acceptance else "dashboard")
|
||||
|
||||
|
||||
func _create_request(key: String, callback: Callable) -> void:
|
||||
|
||||
+23
-3
@@ -15,6 +15,7 @@ var feedback_button: Button
|
||||
var feedback_dialog: Control
|
||||
var notes_dialog: Control
|
||||
var session_expiry_dialog: Control
|
||||
var legal_document_dialog: Control
|
||||
var theme_service
|
||||
var current_screen_name := ""
|
||||
var screens_margin: MarginContainer
|
||||
@@ -40,7 +41,7 @@ func _ready() -> void:
|
||||
theme_service.theme_changed.connect(_on_theme_changed)
|
||||
resized.connect(_update_responsive_layout)
|
||||
_update_responsive_layout()
|
||||
show_screen("dashboard" if AuthState.is_logged_in else "login")
|
||||
show_screen(("legal_consent" if AuthState.requires_legal_acceptance else "dashboard") if AuthState.is_logged_in else "login")
|
||||
|
||||
|
||||
func _update_responsive_layout() -> void:
|
||||
@@ -216,6 +217,12 @@ func _build_ui() -> void:
|
||||
screens_container.add_child(login)
|
||||
screens["login"] = login
|
||||
|
||||
var legal_consent := preload("res://scripts/ui/legal_consent_screen.gd").new()
|
||||
legal_consent.name = "LegalConsentScreen"
|
||||
legal_consent.main = self
|
||||
screens_container.add_child(legal_consent)
|
||||
screens["legal_consent"] = legal_consent
|
||||
|
||||
var dashboard := preload("res://scripts/ui/dashboard_screen.gd").new()
|
||||
dashboard.name = "DashboardScreen"
|
||||
dashboard.main = self
|
||||
@@ -330,10 +337,13 @@ func _build_ui() -> void:
|
||||
session_expiry_dialog.name = "SessionExpiryDialog"
|
||||
session_expiry_dialog.logout_requested.connect(_logout)
|
||||
add_child(session_expiry_dialog)
|
||||
legal_document_dialog = preload("res://scripts/ui/legal_document_dialog.gd").new()
|
||||
legal_document_dialog.name = "LegalDocumentDialog"
|
||||
add_child(legal_document_dialog)
|
||||
_build_account_menu()
|
||||
_build_drawer()
|
||||
|
||||
AuthState.auth_changed.connect(_refresh_header_controls)
|
||||
AuthState.auth_changed.connect(_on_auth_changed)
|
||||
AuthState.session_expired.connect(_on_session_expired)
|
||||
_refresh_header_controls()
|
||||
_build_app_menu()
|
||||
@@ -534,7 +544,7 @@ func _close_account_menu() -> void:
|
||||
|
||||
|
||||
func _screen_uses_shell(screen_name: String) -> bool:
|
||||
return AuthState.is_logged_in and screen_name not in ["login", "player", "ccs_player"]
|
||||
return AuthState.is_logged_in and screen_name not in ["login", "legal_consent", "player", "ccs_player"]
|
||||
|
||||
|
||||
func _update_shell_visibility(screen_name: String) -> void:
|
||||
@@ -706,6 +716,16 @@ func open_feedback_dialog() -> void:
|
||||
feedback_dialog.show_dialog(FeedbackService.build_context(current_screen_name, get_viewport_rect().size))
|
||||
|
||||
|
||||
func open_legal_document(kind: String) -> void:
|
||||
legal_document_dialog.show_document(kind)
|
||||
|
||||
|
||||
func _on_auth_changed() -> void:
|
||||
_refresh_header_controls()
|
||||
if AuthState.is_logged_in and AuthState.requires_legal_acceptance and current_screen_name not in ["login", "legal_consent"]:
|
||||
show_screen("legal_consent")
|
||||
|
||||
|
||||
func open_notes_dialog() -> void:
|
||||
if not AuthState.is_logged_in or notes_dialog == null:
|
||||
return
|
||||
|
||||
@@ -52,6 +52,27 @@ func _ready() -> void:
|
||||
sound_toggle.toggled.connect(UISoundService.set_sounds_enabled)
|
||||
content.add_child(sound_toggle)
|
||||
|
||||
var legal_panel := PanelContainer.new()
|
||||
legal_panel.custom_minimum_size = Vector2(520, 0)
|
||||
legal_panel.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
legal_panel.add_theme_stylebox_override("panel", Brand.panel_box(UIThemeService.is_dark_mode()))
|
||||
legal_panel.set_meta("legal_panel", true)
|
||||
add_child(legal_panel)
|
||||
var legal_content := VBoxContainer.new()
|
||||
legal_content.add_theme_constant_override("separation", 10)
|
||||
legal_panel.add_child(legal_content)
|
||||
var legal_heading := Label.new()
|
||||
legal_heading.text = "Legal"
|
||||
legal_heading.add_theme_font_override("font", Brand.font)
|
||||
legal_heading.add_theme_font_size_override("font_size", 20)
|
||||
legal_content.add_child(legal_heading)
|
||||
for spec in [["Terms of Service", "terms"], ["Privacy Policy", "privacy"]]:
|
||||
var button := Button.new()
|
||||
button.text = spec[0]
|
||||
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
button.pressed.connect(_open_legal_document.bind(str(spec[1])))
|
||||
legal_content.add_child(button)
|
||||
|
||||
UIThemeService.theme_changed.connect(_on_theme_changed)
|
||||
|
||||
|
||||
@@ -64,8 +85,15 @@ func _on_theme_toggled(enabled: bool) -> void:
|
||||
UIThemeService.set_dark_mode(enabled)
|
||||
|
||||
|
||||
func _open_legal_document(kind: String) -> void:
|
||||
main.open_legal_document(kind)
|
||||
|
||||
|
||||
func _on_theme_changed(is_dark: bool) -> void:
|
||||
theme_toggle.set_pressed_no_signal(is_dark)
|
||||
var panel := theme_toggle.get_parent().get_parent() as PanelContainer
|
||||
if panel != null:
|
||||
panel.add_theme_stylebox_override("panel", Brand.panel_box(is_dark))
|
||||
for node in get_children():
|
||||
if node is PanelContainer and node.has_meta("legal_panel"):
|
||||
node.add_theme_stylebox_override("panel", Brand.panel_box(is_dark))
|
||||
|
||||
Reference in New Issue
Block a user