what the fuck
All checks were successful
CI - Build Tonehaus Docker image / tonehaus-ci-build (push) Successful in 1m55s

This commit is contained in:
2025-11-27 20:03:12 +00:00
parent f15d9a9cfd
commit 054e970df9
36 changed files with 1434 additions and 363 deletions

View File

@@ -22,11 +22,14 @@
</button>
<ul class="dropdown-menu dropdown-menu-end">
{% if is_granted('ROLE_ADMIN') %}
<li><a class="dropdown-item" href="{{ path('admin_dashboard') }}">Site dashboard</a></li>
<li><a class="dropdown-item" href="{{ path('admin_settings') }}">Site settings</a></li>
<li><h6 class="dropdown-header">Site</h6></li>
<li><a class="dropdown-item" href="{{ path('admin_dashboard') }}">Site Dashboard</a></li>
<li><a class="dropdown-item" href="{{ path('admin_settings') }}">Site Settings</a></li>
<li><hr class="dropdown-divider"></li>
{% endif %}
<li><h6 class="dropdown-header">User</h6></li>
<li><a class="dropdown-item" href="{{ path('account_dashboard') }}">Dashboard</a></li>
<li><a class="dropdown-item" href="{{ path('account_profile') }}">Profile</a></li>
<li><a class="dropdown-item" href="{{ path('account_settings') }}">Settings</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="{{ path('app_logout') }}">Logout</a></li>

View File

@@ -34,17 +34,27 @@
<div class="card">
<div class="card-body">
<h2 class="h6">Profile</h2>
<div class="row g-3">
<div class="col-sm-6">
<div class="row g-3 align-items-center">
<div class="col-auto">
{% if profileImage %}
<img src="{{ profileImage }}" alt="Profile picture" class="rounded-circle border" width="72" height="72" style="object-fit: cover;">
{% else %}
<div class="rounded-circle bg-secondary d-flex align-items-center justify-content-center text-white" style="width:72px;height:72px;">
<span class="fw-semibold">{{ (displayName ?? email)|slice(0,1)|upper }}</span>
</div>
{% endif %}
</div>
<div class="col-sm-5">
<label class="form-label">Email</label>
<input class="form-control" value="{{ email }}" readonly />
</div>
<div class="col-sm-6">
<div class="col-sm-5">
<label class="form-label">Display name</label>
<input class="form-control" value="{{ displayName }}" readonly />
</div>
</div>
<div class="mt-3">
<a class="btn btn-outline-primary me-2" href="{{ path('account_profile') }}">Edit profile</a>
<a class="btn btn-outline-secondary" href="{{ path('account_password') }}">Change password</a>
</div>
</div>
@@ -57,9 +67,15 @@
<h2 class="h6 mb-3">Your reviews</h2>
<div class="vstack gap-2">
{% for r in userReviews %}
<div>
<div><a href="{{ path('review_show', {id: r.id}) }}" class="text-decoration-none">{{ r.title }}</a> <span class="text-secondary">(Rating {{ r.rating }}/10)</span></div>
<div class="text-secondary small">{{ r.album.name }}{{ r.createdAt|date('Y-m-d H:i') }}</div>
<div class="d-flex justify-content-between align-items-start">
<div class="me-2">
<div><a href="{{ path('review_show', {id: r.id}) }}" class="text-decoration-none">{{ r.title }}</a> <span class="text-secondary">(Rating {{ r.rating }}/10)</span></div>
<div class="text-secondary small">{{ r.album.name }}{{ r.createdAt|date('Y-m-d H:i') }}</div>
</div>
<form method="post" action="{{ path('review_delete', {id: r.id}) }}" onsubmit="return confirm('Delete this review?');">
<input type="hidden" name="_token" value="{{ csrf_token('delete_review_' ~ r.id) }}" />
<button class="btn btn-sm btn-outline-danger" type="submit">Delete</button>
</form>
</div>
{% else %}
<div class="text-secondary">You haven't written any reviews yet.</div>
@@ -74,13 +90,17 @@
<h2 class="h6 mb-3">Your albums</h2>
<div class="vstack gap-2">
{% for a in userAlbums %}
<div class="d-flex justify-content-between">
<div>
<div class="d-flex justify-content-between align-items-start">
<div class="me-2">
<div><a href="{{ path('album_show', {id: a.localId}) }}" class="text-decoration-none">{{ a.name }}</a></div>
<div class="text-secondary small">{{ a.artists|join(', ') }}{% if a.releaseDate %}{{ a.releaseDate }}{% endif %}</div>
</div>
<div class="ms-2">
<div class="d-flex gap-2">
<a class="btn btn-sm btn-outline-secondary" href="{{ path('album_edit', {id: a.localId}) }}">Edit</a>
<form method="post" action="{{ path('album_delete', {id: a.localId}) }}" onsubmit="return confirm('Delete this album?');">
<input type="hidden" name="_token" value="{{ csrf_token('delete-album-' ~ a.localId) }}" />
<button class="btn btn-sm btn-outline-danger" type="submit">Delete</button>
</form>
</div>
</div>
{% else %}

View File

@@ -0,0 +1,46 @@
{% extends 'base.html.twig' %}
{% block title %}Edit Profile{% endblock %}
{% block body %}
<h1 class="h4 mb-3">Edit profile</h1>
{% for msg in app.flashes('success') %}
<div class="alert alert-success">{{ msg }}</div>
{% endfor %}
<div class="row g-4">
<div class="col-md-4">
<div class="card h-100">
<div class="card-body text-center">
<h2 class="h6 mb-3">Current picture</h2>
{% if profileImage %}
<img src="{{ profileImage }}" alt="Profile picture" class="rounded-circle border mb-3" width="160" height="160" style="object-fit: cover;">
{% else %}
<div class="rounded-circle bg-secondary text-white d-inline-flex align-items-center justify-content-center mb-3" style="width:160px;height:160px;">
<span class="fs-3">{{ (app.user.displayName ?? app.user.userIdentifier)|slice(0,1)|upper }}</span>
</div>
{% endif %}
<p class="text-secondary small mb-0">Images up to 4MB. JPG or PNG recommended.</p>
</div>
</div>
</div>
<div class="col-md-8">
<div class="card">
<div class="card-body">
{{ form_start(form, {attr: {novalidate: 'novalidate'}}) }}
<div class="mb-3">{{ form_row(form.email) }}</div>
<div class="mb-3">{{ form_row(form.displayName) }}</div>
<div class="mb-3">{{ form_row(form.profileImage) }}</div>
<hr>
<p class="text-secondary small mb-3">Password change is optional. Provide your current password only if you want to update it.</p>
<div class="mb-3">{{ form_row(form.currentPassword) }}</div>
<div class="mb-3">{{ form_row(form.newPassword) }}</div>
<div class="d-flex gap-2">
<button class="btn btn-success" type="submit">Save changes</button>
<a class="btn btn-link" href="{{ path('account_dashboard') }}">Cancel</a>
</div>
{{ form_end(form) }}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -37,9 +37,15 @@
<h2 class="h6 mb-3">Latest reviews (50)</h2>
<div class="vstack gap-2">
{% for r in recentReviews %}
<div>
<div><a class="text-decoration-none" href="{{ path('review_show', {id: r.id}) }}">{{ r.title }}</a> <span class="text-secondary">(Rating {{ r.rating }}/10)</span></div>
<div class="text-secondary small">{{ r.album.name }} • by {{ r.author.displayName ?? r.author.userIdentifier }}{{ r.createdAt|date('Y-m-d H:i') }}</div>
<div class="d-flex justify-content-between align-items-start">
<div class="me-2">
<div><a class="text-decoration-none" href="{{ path('review_show', {id: r.id}) }}">{{ r.title }}</a> <span class="text-secondary">(Rating {{ r.rating }}/10)</span></div>
<div class="text-secondary small">{{ r.album.name }} • by {{ r.author.displayName ?? r.author.userIdentifier }}{{ r.createdAt|date('Y-m-d H:i') }}</div>
</div>
<form method="post" action="{{ path('review_delete', {id: r.id}) }}" onsubmit="return confirm('Delete this review?');">
<input type="hidden" name="_token" value="{{ csrf_token('delete_review_' ~ r.id) }}" />
<button class="btn btn-sm btn-outline-danger" type="submit">Delete</button>
</form>
</div>
{% else %}
<div class="text-secondary">No reviews.</div>
@@ -55,8 +61,8 @@
<div class="vstack gap-2">
{% for a in recentAlbums %}
{% set publicId = a.source == 'user' ? a.localId : a.spotifyId %}
<div class="d-flex justify-content-between">
<div>
<div class="d-flex justify-content-between align-items-start">
<div class="me-2">
<div>
{% if publicId %}
<a class="text-decoration-none" href="{{ path('album_show', {id: publicId}) }}">{{ a.name }}</a>
@@ -67,6 +73,12 @@
</div>
<div class="text-secondary small">{{ a.artists|join(', ') }}{% if a.releaseDate %}{{ a.releaseDate }}{% endif %}{{ a.createdAt|date('Y-m-d H:i') }}</div>
</div>
{% if publicId %}
<form method="post" action="{{ path('album_delete', {id: publicId}) }}" onsubmit="return confirm('Delete this album?');">
<input type="hidden" name="_token" value="{{ csrf_token('delete-album-' ~ publicId) }}" />
<button class="btn btn-sm btn-outline-danger" type="submit">Delete</button>
</form>
{% endif %}
</div>
{% else %}
<div class="text-secondary">No albums.</div>

View File

@@ -7,7 +7,7 @@
<div>{{ form_label(form.artistsCsv) }}{{ form_widget(form.artistsCsv, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.releaseDate) }}{{ form_widget(form.releaseDate, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.totalTracks) }}{{ form_widget(form.totalTracks, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.coverUrl) }}{{ form_widget(form.coverUrl, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.coverUpload) }}{{ form_widget(form.coverUpload, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.externalUrl) }}{{ form_widget(form.externalUrl, {attr: {class: 'form-control'}}) }}</div>
<button class="btn btn-success" type="submit">Save changes</button>
<a class="btn btn-link" href="{{ path('album_show', {id: albumId}) }}">Cancel</a>

View File

@@ -7,7 +7,7 @@
<div>{{ form_label(form.artistsCsv) }}{{ form_widget(form.artistsCsv, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.releaseDate) }}{{ form_widget(form.releaseDate, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.totalTracks) }}{{ form_widget(form.totalTracks, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.coverUrl) }}{{ form_widget(form.coverUrl, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.coverUpload) }}{{ form_widget(form.coverUpload, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.externalUrl) }}{{ form_widget(form.externalUrl, {attr: {class: 'form-control'}}) }}</div>
<button class="btn btn-success" type="submit">Create</button>
{{ form_end(form) }}