Add nav menu with Characters, NPC's and Storyline pages

Navbar (collapsible) with active-state highlighting driven by a nav list
in base.html.twig. Three routed, auth-gated controllers each rendering a
shared placeholder partial - data models still to be designed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Frank
2026-09-06 14:36:17 +02:00
co-authored by Claude Sonnet 5
parent 9f6763040e
commit cc4fbf425d
8 changed files with 126 additions and 3 deletions
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class CharacterController extends AbstractController
{
#[Route('/characters', name: 'app_characters')]
public function index(): Response
{
return $this->render('characters/index.html.twig');
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class NpcController extends AbstractController
{
#[Route('/npcs', name: 'app_npcs')]
public function index(): Response
{
return $this->render('npc/index.html.twig');
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class StorylineController extends AbstractController
{
#[Route('/storyline', name: 'app_storyline')]
public function index(): Response
{
return $this->render('storyline/index.html.twig');
}
}
@@ -0,0 +1,13 @@
{# Temporary empty state for pages whose data model isn't built yet. #}
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="h3 mb-0">{{ title }}</h1>
</div>
<div class="card shadow-sm">
<div class="card-body text-center py-5">
<div class="display-6 mb-2">{{ icon }}</div>
<p class="text-body-secondary mb-0">
{{ blurb }}
</p>
</div>
</div>
+23
View File
@@ -18,10 +18,33 @@
<div class="container"> <div class="container">
<a class="navbar-brand" href="{{ path('app_home') }}">🎲 dmtools</a> <a class="navbar-brand" href="{{ path('app_home') }}">🎲 dmtools</a>
{% if app.user %} {% if app.user %}
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#mainnav" aria-controls="mainnav" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainnav">
{% set route = app.request.attributes.get('_route') %}
{% set nav = [
{ route: 'app_home', label: 'Overview' },
{ route: 'app_characters', label: 'Characters' },
{ route: 'app_npcs', label: "NPC's" },
{ route: 'app_storyline', label: 'Storyline' },
] %}
<ul class="navbar-nav me-auto">
{% for item in nav %}
{% set active = route is not null and route starts with item.route %}
<li class="nav-item">
<a class="nav-link{{ active ? ' active' }}"{{ active ? ' aria-current="page"' }}
href="{{ path(item.route) }}">{{ item.label }}</a>
</li>
{% endfor %}
</ul>
<div class="d-flex align-items-center gap-3"> <div class="d-flex align-items-center gap-3">
<span class="navbar-text small">{{ app.user.userIdentifier }}</span> <span class="navbar-text small">{{ app.user.userIdentifier }}</span>
<a class="btn btn-outline-secondary btn-sm" href="{{ path('app_logout') }}">Log out</a> <a class="btn btn-outline-secondary btn-sm" href="{{ path('app_logout') }}">Log out</a>
</div> </div>
</div>
{% endif %} {% endif %}
</div> </div>
</nav> </nav>
+11
View File
@@ -0,0 +1,11 @@
{% extends 'base.html.twig' %}
{% block title %}Characters · dmtools{% endblock %}
{% block body %}
{{ include('_partials/page_placeholder.html.twig', {
title: 'Characters',
icon: '🧝',
blurb: 'Player characters will live here — list, sheet details and party overview. Data model to be designed.',
}) }}
{% endblock %}
+11
View File
@@ -0,0 +1,11 @@
{% extends 'base.html.twig' %}
{% block title %}NPC's · dmtools{% endblock %}
{% block body %}
{{ include('_partials/page_placeholder.html.twig', {
title: "NPC's",
icon: '🧙',
blurb: 'Non-player characters will live here — searchable list with faction, location and notes. Data model to be designed.',
}) }}
{% endblock %}
+11
View File
@@ -0,0 +1,11 @@
{% extends 'base.html.twig' %}
{% block title %}Storyline · dmtools{% endblock %}
{% block body %}
{{ include('_partials/page_placeholder.html.twig', {
title: 'Storyline',
icon: '📜',
blurb: 'The campaign storyline will live here. Shape still to be decided — session log, plot threads, or a wiki-style document.',
}) }}
{% endblock %}