Files
Code_of_Conquest/public_web/templates/game/partials/character_panel.html
Phillip Tarrant 61a42d3a77 feat(api,web): tier-based session limits and daily turn usage display
Backend Changes:
- Add tier-based max_sessions config (free: 1, basic: 2, premium: 3, elite: 5)
- Add DELETE /api/v1/sessions/{id} endpoint for hard session deletion
- Cascade delete chat messages when session is deleted
- Add GET /api/v1/usage endpoint for daily turn limit info
- Replace hardcoded TIER_LIMITS with config-based ai_calls_per_day
- Handle unlimited (-1) tier in rate limiter service

Frontend Changes:
- Add inline session delete buttons with HTMX on character list
- Add usage_display.html component showing remaining daily turns
- Display usage indicator on character list and game play pages
- Page refresh after session deletion to update UI state

Documentation:
- Update API_REFERENCE.md with new endpoints and tier limits
- Update API_TESTING.md with session endpoint examples
- Update SESSION_MANAGEMENT.md with tier-based limits

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 10:00:45 -06:00

190 lines
8.9 KiB
HTML

{#
Character Panel - Left sidebar
Displays character stats, resource bars, and action buttons
#}
<div class="character-panel">
{# Character Header #}
<div class="character-header">
<div class="character-name">{{ character.name }}</div>
<div class="character-info">
<span class="character-class">{{ character.class_name }}</span>
<span class="character-level">Level {{ character.level }}</span>
</div>
<div class="character-usage">
{% include 'components/usage_display.html' %}
</div>
</div>
{# Resource Bars #}
<div class="resource-bars">
{# HP Bar #}
<div class="resource-bar resource-bar--hp">
<div class="resource-bar-label">
<span class="resource-bar-name">HP</span>
<span class="resource-bar-value">{{ character.current_hp }} / {{ character.max_hp }}</span>
</div>
<div class="resource-bar-track">
{% set hp_percent = (character.current_hp / character.max_hp * 100)|int %}
<div class="resource-bar-fill" style="width: {{ hp_percent }}%"></div>
</div>
</div>
{# MP Bar #}
<div class="resource-bar resource-bar--mp">
<div class="resource-bar-label">
<span class="resource-bar-name">MP</span>
<span class="resource-bar-value">{{ character.current_mp }} / {{ character.max_mp }}</span>
</div>
<div class="resource-bar-track">
{% set mp_percent = (character.current_mp / character.max_mp * 100)|int %}
<div class="resource-bar-fill" style="width: {{ mp_percent }}%"></div>
</div>
</div>
</div>
{# Stats Accordion (Collapsed by default) #}
<div class="panel-accordion collapsed" data-panel-accordion="stats">
<button class="panel-accordion-header" onclick="togglePanelAccordion(this)">
<span>Stats</span>
<span class="panel-accordion-icon"></span>
</button>
<div class="panel-accordion-content">
<div class="stats-grid">
<div class="stat-item">
<div class="stat-abbr">STR</div>
<div class="stat-value">{{ character.stats.strength }}</div>
</div>
<div class="stat-item">
<div class="stat-abbr">DEX</div>
<div class="stat-value">{{ character.stats.dexterity }}</div>
</div>
<div class="stat-item">
<div class="stat-abbr">CON</div>
<div class="stat-value">{{ character.stats.constitution }}</div>
</div>
<div class="stat-item">
<div class="stat-abbr">INT</div>
<div class="stat-value">{{ character.stats.intelligence }}</div>
</div>
<div class="stat-item">
<div class="stat-abbr">WIS</div>
<div class="stat-value">{{ character.stats.wisdom }}</div>
</div>
<div class="stat-item">
<div class="stat-abbr">CHA</div>
<div class="stat-value">{{ character.stats.charisma }}</div>
</div>
</div>
</div>
</div>
{# Quick Actions (Equipment, NPC, Travel) #}
<div class="quick-actions">
{# Equipment & Gear - Opens modal #}
<button class="action-btn action-btn--special"
hx-get="{{ url_for('game.equipment_modal', session_id=session_id) }}"
hx-target="#modal-container"
hx-swap="innerHTML">
⚔️ Equipment & Gear
</button>
{# Talk to NPC - Opens NPC accordion #}
<button class="action-btn action-btn--special"
hx-get="{{ url_for('game.npcs_accordion', session_id=session_id) }}"
hx-target="#accordion-npcs"
hx-swap="innerHTML"
onclick="document.querySelector('[data-accordion=npcs]').classList.remove('collapsed')">
💬 Talk to NPC...
</button>
{# Travel - Opens modal #}
<button class="action-btn action-btn--special"
hx-get="{{ url_for('game.travel_modal', session_id=session_id) }}"
hx-target="#modal-container"
hx-swap="innerHTML">
🗺️ Travel to...
</button>
</div>
{# Actions Section #}
<div class="actions-section">
<div class="actions-title">Actions</div>
{# Free Tier Actions #}
<div class="actions-tier">
<div class="actions-tier-label">Free Actions</div>
<div class="actions-list">
{% for action in actions.free %}
{% set available = action.context == ['any'] or location.location_type in action.context %}
<button class="action-btn action-btn--free"
hx-post="{{ url_for('game.take_action', session_id=session_id) }}"
hx-vals='{"action_type": "button", "prompt_id": "{{ action.prompt_id }}"}'
hx-target="#narrative-content"
hx-swap="innerHTML"
hx-disabled-elt="this"
{% if not available %}disabled title="Not available in this location"{% endif %}>
<span class="action-btn-text">{{ action.display_text }}</span>
{% if action.cooldown %}
<span class="action-btn-cooldown" title="{{ action.cooldown }} turn cooldown"></span>
{% endif %}
</button>
{% endfor %}
</div>
</div>
{# Premium Tier Actions #}
<div class="actions-tier">
<div class="actions-tier-label">Premium Actions</div>
<div class="actions-list">
{% for action in actions.premium %}
{% set available = action.context == ['any'] or location.location_type in action.context %}
{% set locked = user_tier not in ['premium', 'elite'] %}
<button class="action-btn {% if locked %}action-btn--locked{% else %}action-btn--premium{% endif %}"
{% if not locked %}
hx-post="{{ url_for('game.take_action', session_id=session_id) }}"
hx-vals='{"action_type": "button", "prompt_id": "{{ action.prompt_id }}"}'
hx-target="#narrative-content"
hx-swap="innerHTML"
hx-disabled-elt="this"
{% endif %}
{% if locked %}disabled title="Requires Premium tier"{% elif not available %}disabled title="Not available in this location"{% endif %}>
<span class="action-btn-text">{{ action.display_text }}</span>
{% if locked %}
<span class="action-btn-lock">🔒</span>
{% elif action.cooldown %}
<span class="action-btn-cooldown" title="{{ action.cooldown }} turn cooldown"></span>
{% endif %}
</button>
{% endfor %}
</div>
</div>
{# Elite Tier Actions #}
<div class="actions-tier">
<div class="actions-tier-label">Elite Actions</div>
<div class="actions-list">
{% for action in actions.elite %}
{% set available = action.context == ['any'] or location.location_type in action.context %}
{% set locked = user_tier != 'elite' %}
<button class="action-btn {% if locked %}action-btn--locked{% else %}action-btn--elite{% endif %}"
{% if not locked %}
hx-post="{{ url_for('game.take_action', session_id=session_id) }}"
hx-vals='{"action_type": "button", "prompt_id": "{{ action.prompt_id }}"}'
hx-target="#narrative-content"
hx-swap="innerHTML"
hx-disabled-elt="this"
{% endif %}
{% if locked %}disabled title="Requires Elite tier"{% elif not available %}disabled title="Not available in this location"{% endif %}>
<span class="action-btn-text">{{ action.display_text }}</span>
{% if locked %}
<span class="action-btn-lock">🔒</span>
{% elif action.cooldown %}
<span class="action-btn-cooldown" title="{{ action.cooldown }} turn cooldown"></span>
{% endif %}
</button>
{% endfor %}
</div>
</div>
</div>
</div>