feat: gravatar integration
This commit is contained in:
@@ -0,0 +1,17 @@
|
|||||||
|
import {Controller} from '@hotwired/stimulus'
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static values = {url: String, alt: String}
|
||||||
|
static classes = ['image']
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
const img = new Image()
|
||||||
|
img.onload = () => {
|
||||||
|
img.classList.add(...this.imageClasses)
|
||||||
|
this.element.innerHTML = ''
|
||||||
|
this.element.appendChild(img)
|
||||||
|
}
|
||||||
|
img.alt = this.altValue
|
||||||
|
img.src = this.urlValue
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ class ParticipantCardDataService
|
|||||||
/**
|
/**
|
||||||
* Get card data for a single participant.
|
* Get card data for a single participant.
|
||||||
*
|
*
|
||||||
* @return array{name: string, roomName: string, price: string, isCanceled: bool}
|
* @return array{name: string, email: string, roomName: string, price: string, isCanceled: bool}
|
||||||
*/
|
*/
|
||||||
public function getCardData(BookingDto $bookingDto, int $index): array
|
public function getCardData(BookingDto $bookingDto, int $index): array
|
||||||
{
|
{
|
||||||
@@ -38,6 +38,9 @@ class ParticipantCardDataService
|
|||||||
// Extract participant name with fallback
|
// Extract participant name with fallback
|
||||||
$name = $this->getParticipantName($participant, $index);
|
$name = $this->getParticipantName($participant, $index);
|
||||||
|
|
||||||
|
// Extract email
|
||||||
|
$email = $participant->email ?? '';
|
||||||
|
|
||||||
// Extract room name
|
// Extract room name
|
||||||
$roomName = $this->getRoomName($bookingDto, $participant);
|
$roomName = $this->getRoomName($bookingDto, $participant);
|
||||||
|
|
||||||
@@ -49,6 +52,7 @@ class ParticipantCardDataService
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
|
'email' => $email,
|
||||||
'roomName' => $roomName,
|
'roomName' => $roomName,
|
||||||
'price' => $price,
|
'price' => $price,
|
||||||
'isCanceled' => $isCanceled,
|
'isCanceled' => $isCanceled,
|
||||||
@@ -58,7 +62,7 @@ class ParticipantCardDataService
|
|||||||
/**
|
/**
|
||||||
* Get card data for all participants.
|
* Get card data for all participants.
|
||||||
*
|
*
|
||||||
* @return array<int, array{name: string, roomName: string, price: string, isCanceled: bool}>
|
* @return array<int, array{name: string, email: string, roomName: string, price: string, isCanceled: bool}>
|
||||||
*/
|
*/
|
||||||
public function getAllCardsData(BookingDto $bookingDto): array
|
public function getAllCardsData(BookingDto $bookingDto): array
|
||||||
{
|
{
|
||||||
@@ -132,7 +136,7 @@ class ParticipantCardDataService
|
|||||||
/**
|
/**
|
||||||
* Get card data for a single participant with validation state.
|
* Get card data for a single participant with validation state.
|
||||||
*
|
*
|
||||||
* @return array{name: string, roomName: string, price: string, isCanceled: bool, isValid: bool, errorMessages: array<string>}
|
* @return array{name: string, email: string, roomName: string, price: string, isCanceled: bool, isValid: bool, errorMessages: array<string>}
|
||||||
*/
|
*/
|
||||||
public function getCardDataWithValidation(BookingDto $bookingDto, int $index, array $validationGroups): array
|
public function getCardDataWithValidation(BookingDto $bookingDto, int $index, array $validationGroups): array
|
||||||
{
|
{
|
||||||
@@ -171,7 +175,7 @@ class ParticipantCardDataService
|
|||||||
/**
|
/**
|
||||||
* Get card data for all participants with validation state.
|
* Get card data for all participants with validation state.
|
||||||
*
|
*
|
||||||
* @return array<int, array{name: string, roomName: string, price: string, isCanceled: bool, isValid: bool, errorMessages: array<string>}>
|
* @return array<int, array{name: string, email: string, roomName: string, price: string, isCanceled: bool, isValid: bool, errorMessages: array<string>}>
|
||||||
*/
|
*/
|
||||||
public function getAllCardsDataWithValidation(BookingDto $bookingDto, array $validationGroups): array
|
public function getAllCardsDataWithValidation(BookingDto $bookingDto, array $validationGroups): array
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Twig;
|
||||||
|
|
||||||
|
use Twig\Extension\AbstractExtension;
|
||||||
|
use Twig\TwigFunction;
|
||||||
|
|
||||||
|
final class GravatarExtension extends AbstractExtension
|
||||||
|
{
|
||||||
|
public function getFunctions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new TwigFunction('gravatar_url', [$this, 'getGravatarUrl']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a Gravatar URL for the given email address.
|
||||||
|
*/
|
||||||
|
public function getGravatarUrl(string $email, int $size = 80): string
|
||||||
|
{
|
||||||
|
$hash = md5(strtolower(trim($email)));
|
||||||
|
|
||||||
|
return sprintf('https://www.gravatar.com/avatar/%s?s=%d&d=404', $hash, $size);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,22 +8,36 @@
|
|||||||
class="{{ html_classes('border rounded p-4', { 'border-gray-400 bg-gray-50': isCanceled, 'border-red-700': not isValid }) }}">
|
class="{{ html_classes('border rounded p-4', { 'border-gray-400 bg-gray-50': isCanceled, 'border-red-700': not isValid }) }}">
|
||||||
<div class="flex justify-between items-start">
|
<div class="flex justify-between items-start">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-start gap-3">
|
||||||
<h3 class="font-semibold {{ isCanceled ? 'text-gray-600' : '' }}" {{ qa_attribute('participant-name', index) }}>
|
<div class="flex-shrink-0"
|
||||||
{{ cardData.name }}
|
{{ stimulus_controller('gravatar', {
|
||||||
</h3>
|
url: gravatar_url(cardData.email),
|
||||||
{% if isCanceled %}
|
alt: cardData.name
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-700 text-white" {{ qa_attribute('participant-canceled', index) }}>
|
}, {
|
||||||
storniert
|
image: 'w-12 h-12 rounded-full'
|
||||||
</span>
|
}) }}
|
||||||
{% endif %}
|
{{ qa_attribute('participant-avatar', index) }}>
|
||||||
{% if not isValid %}
|
{{ icon('user', 'w-12 h-12 text-gray-400') }}
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-700 text-white" {{ qa_attribute('participant-invalid', index) }}>
|
</div>
|
||||||
unvollständige oder fehlerhafte Daten
|
<div class="flex-1">
|
||||||
</span>
|
<div class="flex items-center gap-2">
|
||||||
{% endif %}
|
<h3 class="font-semibold {{ isCanceled ? 'text-gray-600' : '' }}" {{ qa_attribute('participant-name', index) }}>
|
||||||
|
{{ cardData.name }}
|
||||||
|
</h3>
|
||||||
|
{% if isCanceled %}
|
||||||
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-700 text-white" {{ qa_attribute('participant-canceled', index) }}>
|
||||||
|
storniert
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if not isValid %}
|
||||||
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-700 text-white" {{ qa_attribute('participant-invalid', index) }}>
|
||||||
|
unvollständige oder fehlerhafte Daten
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-gray-600" {{ qa_attribute('participant-room-name', index) }}>{{ cardData.roomName }}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-600" {{ qa_attribute('participant-room-name', index) }}>{{ cardData.roomName }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<span class="font-medium {{ isCanceled ? 'text-gray-500' : '' }}" {{ qa_attribute('participant-price', index) }}>{{ cardData.price }}</span>
|
<span class="font-medium {{ isCanceled ? 'text-gray-500' : '' }}" {{ qa_attribute('participant-price', index) }}>{{ cardData.price }}</span>
|
||||||
|
|||||||
@@ -45,7 +45,18 @@
|
|||||||
{% block participant_form %}
|
{% block participant_form %}
|
||||||
{% import _self as macros %}
|
{% import _self as macros %}
|
||||||
<div id="participant-form-view">
|
<div id="participant-form-view">
|
||||||
<h2>{{ participantIndex == 0 ? 'Anmelder:in' : 'Teilnehmer:in ' ~ (participantIndex + 1) }}</h2>
|
<div class="flex items-center gap-4 mb-6">
|
||||||
|
<div class="flex-shrink-0"
|
||||||
|
{{ stimulus_controller('gravatar', {
|
||||||
|
url: gravatar_url(form.vars.data.participant.email ?? ''),
|
||||||
|
alt: participantIndex == 0 ? 'Anmelder:in' : 'Teilnehmer:in ' ~ (participantIndex + 1)
|
||||||
|
}, {
|
||||||
|
image: 'w-16 h-16 rounded-full'
|
||||||
|
}) }}>
|
||||||
|
{{ icon('user', 'w-16 h-16 text-gray-400') }}
|
||||||
|
</div>
|
||||||
|
<h2 class="text-2xl">{{ participantIndex == 0 ? 'Anmelder:in' : 'Teilnehmer:in ' ~ (participantIndex + 1) }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{ form_start(form, {
|
{{ form_start(form, {
|
||||||
'attr': {
|
'attr': {
|
||||||
|
|||||||
Reference in New Issue
Block a user