Add live RZ Points balance widget to question and shell headers

Wires the recently-merged question-completion-credits backend
(GET /api/credits) into the client for the first time: a new
CreditsService autoload fetches/caches the balance, and the embedded
credits field on first-answer responses updates it instantly with no
extra round trip. A shared CreditsBadge widget (star icon, balance,
decorative chevron per mockup) surfaces it in both the question
screen toolbar and the main shell header.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
hz
2026-07-28 19:33:33 -05:00
co-authored by Claude Sonnet 5
parent 05a5298ed1
commit efae7e0b2b
6 changed files with 181 additions and 3 deletions
+91
View File
@@ -0,0 +1,91 @@
extends PanelContainer
const Brand := preload("res://scripts/ui/medmind_style.gd")
const STAR_ICON := preload("res://assets/icons/lucide/star.svg")
const CHEVRON_ICON := preload("res://assets/icons/lucide/chevron-down.svg")
var icon_panel: PanelContainer
var icon_texture: TextureRect
var value_label: Label
var caption_label: Label
func _ready() -> void:
custom_minimum_size = Vector2(150, 44)
size_flags_vertical = Control.SIZE_SHRINK_CENTER
_build_ui()
refresh_theme_controls()
_on_credits_changed()
CreditsService.credits_changed.connect(_on_credits_changed)
func _build_ui() -> void:
var margin := MarginContainer.new()
margin.add_theme_constant_override("margin_left", 10)
margin.add_theme_constant_override("margin_right", 10)
margin.add_theme_constant_override("margin_top", 6)
margin.add_theme_constant_override("margin_bottom", 6)
add_child(margin)
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 8)
margin.add_child(row)
icon_panel = PanelContainer.new()
icon_panel.custom_minimum_size = Vector2(28, 28)
icon_panel.size_flags_vertical = Control.SIZE_SHRINK_CENTER
icon_texture = TextureRect.new()
icon_texture.texture = STAR_ICON
icon_texture.custom_minimum_size = Vector2(16, 16)
icon_texture.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
icon_texture.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
icon_panel.add_child(icon_texture)
row.add_child(icon_panel)
var copy := VBoxContainer.new()
copy.size_flags_vertical = Control.SIZE_SHRINK_CENTER
copy.add_theme_constant_override("separation", 0)
value_label = Label.new()
value_label.add_theme_font_override("font", Brand.font)
value_label.add_theme_font_size_override("font_size", 15)
copy.add_child(value_label)
caption_label = Label.new()
caption_label.text = "RZ Points"
caption_label.add_theme_font_override("font", Brand.font)
caption_label.add_theme_font_size_override("font_size", 10)
copy.add_child(caption_label)
row.add_child(copy)
var chevron := TextureRect.new()
chevron.texture = CHEVRON_ICON
chevron.custom_minimum_size = Vector2(14, 14)
chevron.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
chevron.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
chevron.mouse_filter = Control.MOUSE_FILTER_IGNORE
chevron.size_flags_vertical = Control.SIZE_SHRINK_CENTER
row.add_child(chevron)
func refresh_theme_controls() -> void:
var dark := UIThemeService.is_dark_mode()
add_theme_stylebox_override("panel", Brand.content_card_box(dark, false, Brand.AMBER))
icon_panel.add_theme_stylebox_override("panel", Brand.icon_circle_box(dark, Brand.AMBER, 14))
icon_texture.modulate = Brand.AMBER
value_label.add_theme_color_override("font_color", Brand.text_color(dark))
caption_label.add_theme_color_override("font_color", Brand.muted_color(dark))
func _on_credits_changed() -> void:
value_label.text = _format_thousands(CreditsService.credits) if CreditsService.loaded else "--"
func _format_thousands(value: int) -> String:
var digits := str(absi(value))
var grouped := ""
var count := 0
for index in range(digits.length() - 1, -1, -1):
grouped = digits[index] + grouped
count += 1
if count % 3 == 0 and index != 0:
grouped = "," + grouped
return ("-" if value < 0 else "") + grouped