Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions site_novo/app/Http/Controllers/EventController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ class EventController extends Controller
{
public function index()
{
$events = Event::published()->upcoming()->orderBy('starts_at')->get();
$upcomingEvents = Event::published()->upcoming()->orderBy('starts_at')->get();

return view('events.index', ['events' => $events]);
return view('events.index', ['upcomingEvents' => $upcomingEvents]);
}
}
45 changes: 45 additions & 0 deletions site_novo/app/Livewire/Events/EventList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace App\Livewire\Events;

use App\Models\Event;
use Livewire\Component;

class EventList extends Component
{
public string $filter = 'all';

public $events = [];

protected $queryString = [
'filter' => ['except' => 'all'],
];

public function mount(): void
{
$this->loadEvents();
}

public function updatedFilter(): void
{
$this->loadEvents();
}

private function loadEvents(): void
{
$query = Event::published();

match ($this->filter) {
'upcoming' => $query->upcoming()->orderBy('starts_at'),
'past' => $query->past()->orderBy('starts_at', 'desc'),
default => $query->orderBy('starts_at', 'desc'),
};

$this->events = $query->get();
}

public function render()
{
return view('livewire.events.event-list');
}
}
9 changes: 9 additions & 0 deletions site_novo/app/Models/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ public function scopeUpcoming(Builder $query): Builder
return $query->where('starts_at', '>=', now());
}

/**
* @param Builder<Event> $query
* @return Builder<Event>
*/
public function scopePast(Builder $query): Builder
{
return $query->where('starts_at', '<', now());
}

public function getBanner(): ?string
{
if ($this->banner) {
Expand Down
26 changes: 26 additions & 0 deletions site_novo/resources/views/components/landing/event-card.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@props(['event', 'index' => 0])

@php $isPast = $event->starts_at->isPast(); @endphp

<article {{ $attributes->merge(['class' => 'relative snap-start rounded-[10px] overflow-hidden bg-[#0c3a68] border border-white/[0.08] transition-transform hover:-translate-y-1']) }}>
@if ($isPast)
<div class="absolute top-0 right-0 z-[1] bg-white/15 backdrop-blur-sm text-white text-[10px] font-bold px-3 py-1 rounded-bl-lg">
ENCERRADO
</div>
@endif
<div @class([
'aspect-[16/10] bg-cover bg-center',
'opacity-60' => $isPast,
'bg-[radial-gradient(circle_at_30%_40%,#3a6a9c,#0a2a4a_70%)]' => $index % 4 === 0,
'bg-[radial-gradient(circle_at_70%_30%,#4b7aa6,#0a2a4a_70%)]' => $index % 4 === 1,
'bg-[radial-gradient(circle_at_50%_60%,#2f5f94,#07223f_70%)]' => $index % 4 === 2,
'bg-[radial-gradient(circle_at_20%_70%,#3f6e9e,#08254a_70%)]' => $index % 4 === 3,
]) @style([
"background-image: url('{$event->getBanner()}')" => $event->getBanner(),
])></div>
<div class="p-[10px_12px_12px] grid gap-px">
<h4 class="text-white text-[12.5px] font-semibold">{{ $event->title }}</h4>
<span class="text-[11px] text-[#b6c9de]">{{ $event->location }}</span>
<time class="text-[11px] text-azul-claro">{{ mb_convert_case($event->starts_at->translatedFormat('M Y'), MB_CASE_TITLE, 'UTF-8') }}</time>
</div>
</article>
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@

public function mount(): void
{
$this->events = Event::published()->orderBy('starts_at', 'desc')->take(8)->get();
$this->events = Event::published()->upcoming()->orderBy('starts_at')->take(8)->get();

if ($this->events->isEmpty()) {
$this->events = Event::published()->past()->orderBy('starts_at', 'desc')->take(8)->get();
}
}
};
?>
Expand All @@ -29,7 +33,16 @@ class="w-full sm:w-auto justify-center inline-flex items-center gap-2.5 px-[18px
</a>
</div>

@if (count($events) > 0)
@if (count($events) === 0)
<p class="text-center text-white/70">Nenhum evento publicado no momento. Volte em breve!</p>
@elseif (count($events) <= 4)
{{-- Poucos eventos: grade simples, sem carrossel (evita cards soltos e setas sem função) --}}
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
@foreach ($events as $i => $event)
<x-landing.event-card :event="$event" :index="$i" />
@endforeach
</div>
@else
<div x-data="{
step() {
const card = this.$refs.track.querySelector(':scope > *');
Expand All @@ -45,22 +58,7 @@ class="hidden sm:grid place-items-center absolute top-[40%] -left-[18px] z-[5] w

<div x-ref="track" class="no-scrollbar grid grid-flow-col [grid-auto-columns:82%] sm:[grid-auto-columns:calc((100%-16px)/2)] lg:[grid-auto-columns:calc((100%-48px)/4)] gap-4 overflow-x-auto snap-x snap-mandatory scroll-smooth pb-1">
@foreach ($events as $i => $event)
<article class="snap-start rounded-[10px] overflow-hidden bg-[#0c3a68] border border-white/[0.08] transition-transform hover:-translate-y-1">
<div @class([
'aspect-[16/10] bg-cover bg-center',
'bg-[radial-gradient(circle_at_30%_40%,#3a6a9c,#0a2a4a_70%)]' => $i % 4 === 0,
'bg-[radial-gradient(circle_at_70%_30%,#4b7aa6,#0a2a4a_70%)]' => $i % 4 === 1,
'bg-[radial-gradient(circle_at_50%_60%,#2f5f94,#07223f_70%)]' => $i % 4 === 2,
'bg-[radial-gradient(circle_at_20%_70%,#3f6e9e,#08254a_70%)]' => $i % 4 === 3,
]) @style([
"background-image: url('{$event->getBanner()}')" => $event->getBanner(),
])></div>
<div class="p-[10px_12px_12px] grid gap-px">
<h4 class="text-white text-[12.5px] font-semibold">{{ $event->title }}</h4>
<span class="text-[11px] text-[#b6c9de]">{{ $event->location }}</span>
<time class="text-[11px] text-azul-claro">{{ mb_convert_case($event->starts_at->translatedFormat('M Y'), MB_CASE_TITLE, 'UTF-8') }}</time>
</div>
</article>
<x-landing.event-card :event="$event" :index="$i" />
@endforeach
</div>

Expand All @@ -69,8 +67,6 @@ class="hidden sm:grid place-items-center absolute top-[40%] -right-[18px] z-[5]
<x-phosphor-caret-right class="w-4 h-4" />
</button>
</div>
@else
<p class="text-center text-white/70">Nenhum evento publicado no momento. Volte em breve!</p>
@endif
</div>
</section>
80 changes: 4 additions & 76 deletions site_novo/resources/views/events/index.blade.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<x-layouts.app
title="Eventos - PHP-PB"
description="Participe dos meetups, workshops e conferências da comunidade PHP da Paraíba. Confira a agenda de próximos eventos."
description="Participe dos meetups, workshops e conferências da comunidade PHP da Paraíba. Confira a agenda de eventos passados e futuros."
>
@if ($events->count() > 0)
@if ($upcomingEvents->count() > 0)
<x-slot:head>
<script type="application/ld+json">
{!! json_encode(
$events->map(fn ($event) => [
$upcomingEvents->map(fn ($event) => [
'@'.'context' => 'https://schema.org',
'@type' => 'Event',
'name' => $event->title,
Expand Down Expand Up @@ -50,79 +50,7 @@
</div>
</div>

<div class="max-w-[1200px] mx-auto px-6 py-12 bg-areia">
@if ($events->count() > 0)
<div class="space-y-6">
@foreach ($events as $event)
<article @class([
'group relative bg-white rounded-2xl shadow-[0_4px_16px_rgba(8,45,82,.07)] hover:shadow-[0_10px_30px_rgba(8,45,82,.1)] hover:-translate-y-1 transition-all overflow-hidden',
'ring-2 ring-amarelo-nordeste' => $event->is_featured,
])>
@if ($event->is_featured)
<div class="absolute top-0 right-0 bg-amarelo-nordeste text-preto text-xs font-bold px-4 py-1 rounded-bl-xl">
DESTAQUE
</div>
@endif

<div class="grid lg:grid-cols-12 gap-6 p-6 lg:p-8">
<div class="lg:col-span-2">
<div class="inline-flex lg:flex lg:flex-col items-center lg:items-stretch bg-azul-fundo rounded-xl p-4 text-center gap-1">
<div class="font-display text-4xl lg:text-5xl font-bold text-azul-marinho">{{ $event->starts_at->format('d') }}</div>
<div class="text-azul-principal font-semibold text-sm uppercase">{{ $event->starts_at->translatedFormat('M') }}</div>
<div class="text-cinza text-xs">{{ $event->starts_at->format('Y') }}</div>
</div>
</div>

<div class="lg:col-span-7">
<h2 class="font-display text-2xl font-bold text-preto mb-3">{{ $event->title }}</h2>
@if ($event->description)
<p class="text-cinza mb-4">{{ $event->description }}</p>
@endif

<div class="flex flex-wrap items-center gap-4 text-sm">
<div class="flex items-center text-cinza">
<x-phosphor-clock class="w-5 h-5 mr-2 text-azul-principal" />
{{ $event->starts_at->format('H:i') }}
</div>

<div class="flex items-center text-cinza">
<x-phosphor-map-pin class="w-5 h-5 mr-2 text-azul-principal" />
{{ $event->location }}
</div>

<span class="px-3 py-1 bg-azul-fundo text-azul-principal rounded-full text-xs font-semibold">
{{ $event->type->getLabel() }}
</span>
</div>
</div>

<div class="lg:col-span-3 flex lg:flex-col items-center lg:items-end justify-between lg:justify-center gap-4">
@if ($event->attendees_count)
<div class="text-center">
<div class="font-display text-3xl font-bold text-azul-marinho">{{ $event->attendees_count }}</div>
<div class="text-cinza text-xs">confirmados</div>
</div>
@endif

@if ($event->external_url)
<a href="{{ $event->external_url }}" target="_blank" rel="noopener"
class="inline-flex items-center justify-center gap-2 px-6 py-3 bg-azul-principal hover:bg-azul-marinho active:scale-[0.98] text-white font-semibold rounded-full transition-all text-center whitespace-nowrap focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-azul-claro">
Confirmar Presença
<x-phosphor-arrow-right class="w-4 h-4" />
</a>
@endif
</div>
</div>
</article>
@endforeach
</div>
@else
<div class="text-center py-24 text-cinza">
<x-phosphor-calendar-blank class="w-12 h-12 mx-auto mb-4 opacity-50" />
<p class="text-lg font-medium">Nenhum evento agendado no momento</p>
</div>
@endif
</div>
<livewire:events.event-list />
</main>

<livewire:landing.footer />
Expand Down
116 changes: 116 additions & 0 deletions site_novo/resources/views/livewire/events/event-list.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<div class="max-w-[1200px] mx-auto px-6 py-12 bg-areia">
<div class="flex flex-wrap gap-2 mb-8">
<button wire:click="$set('filter', 'all')" type="button"
@class([
'px-4 py-2 rounded-full text-sm font-semibold transition-all',
'bg-azul-principal text-white' => $filter === 'all',
'bg-white text-cinza border border-cinza-claro hover:border-azul-principal' => $filter !== 'all',
])>
Todos
</button>
<button wire:click="$set('filter', 'upcoming')" type="button"
@class([
'px-4 py-2 rounded-full text-sm font-semibold transition-all',
'bg-azul-principal text-white' => $filter === 'upcoming',
'bg-white text-cinza border border-cinza-claro hover:border-azul-principal' => $filter !== 'upcoming',
])>
Próximos
</button>
<button wire:click="$set('filter', 'past')" type="button"
@class([
'px-4 py-2 rounded-full text-sm font-semibold transition-all',
'bg-azul-principal text-white' => $filter === 'past',
'bg-white text-cinza border border-cinza-claro hover:border-azul-principal' => $filter !== 'past',
])>
Passados
</button>
</div>

@if (count($events) > 0)
<div class="space-y-6">
@foreach ($events as $event)
@php $isPast = $event->starts_at->isPast(); @endphp
<article wire:key="event-{{ $event->id }}" @class([
'group relative bg-white rounded-2xl shadow-[0_4px_16px_rgba(8,45,82,.07)] hover:shadow-[0_10px_30px_rgba(8,45,82,.1)] hover:-translate-y-1 transition-all overflow-hidden',
'ring-2 ring-amarelo-nordeste' => $event->is_featured && ! $isPast,
'opacity-70' => $isPast,
])>
@if ($event->is_featured && ! $isPast)
<div class="absolute top-0 right-0 bg-amarelo-nordeste text-preto text-xs font-bold px-4 py-1 rounded-bl-xl">
DESTAQUE
</div>
@elseif ($isPast)
<div class="absolute top-0 right-0 bg-cinza text-white text-xs font-bold px-4 py-1 rounded-bl-xl">
ENCERRADO
</div>
@endif

<div class="grid lg:grid-cols-12 gap-6 p-6 lg:p-8">
<div class="lg:col-span-2">
<div class="inline-flex lg:flex lg:flex-col items-center lg:items-stretch bg-azul-fundo rounded-xl p-4 text-center gap-1">
<div class="font-display text-4xl lg:text-5xl font-bold text-azul-marinho">{{ $event->starts_at->format('d') }}</div>
<div class="text-azul-principal font-semibold text-sm uppercase">{{ $event->starts_at->translatedFormat('M') }}</div>
<div class="text-cinza text-xs">{{ $event->starts_at->format('Y') }}</div>
</div>
</div>

<div class="lg:col-span-7">
<h2 class="font-display text-2xl font-bold text-preto mb-3">{{ $event->title }}</h2>
@if ($event->description)
<p class="text-cinza mb-4">{{ $event->description }}</p>
@endif

<div class="flex flex-wrap items-center gap-4 text-sm">
<div class="flex items-center text-cinza">
<x-phosphor-clock class="w-5 h-5 mr-2 text-azul-principal" />
{{ $event->starts_at->format('H:i') }}
</div>

<div class="flex items-center text-cinza">
<x-phosphor-map-pin class="w-5 h-5 mr-2 text-azul-principal" />
{{ $event->location }}
</div>

<span class="px-3 py-1 bg-azul-fundo text-azul-principal rounded-full text-xs font-semibold">
{{ $event->type->getLabel() }}
</span>
</div>
</div>

<div class="lg:col-span-3 flex lg:flex-col items-center lg:items-end justify-between lg:justify-center gap-4">
@if ($event->attendees_count)
<div class="text-center">
<div class="font-display text-3xl font-bold text-azul-marinho">{{ $event->attendees_count }}</div>
<div class="text-cinza text-xs">confirmados</div>
</div>
@endif

@if ($isPast)
<span class="text-cinza text-sm font-medium">Evento encerrado</span>
@elseif ($event->external_url)
<a href="{{ $event->external_url }}" target="_blank" rel="noopener"
class="inline-flex items-center justify-center gap-2 px-6 py-3 bg-azul-principal hover:bg-azul-marinho active:scale-[0.98] text-white font-semibold rounded-full transition-all text-center whitespace-nowrap focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-azul-claro">
Confirmar Presença
<x-phosphor-arrow-right class="w-4 h-4" />
</a>
@endif
</div>
</div>
</article>
@endforeach
</div>
@else
<div class="text-center py-24 text-cinza">
<x-phosphor-calendar-blank class="w-12 h-12 mx-auto mb-4 opacity-50" />
<p class="text-lg font-medium">
@if ($filter === 'upcoming')
Nenhum evento futuro agendado no momento
@elseif ($filter === 'past')
Nenhum evento passado registrado ainda
@else
Nenhum evento publicado no momento
@endif
</p>
</div>
@endif
</div>
6 changes: 3 additions & 3 deletions site_novo/tests/Feature/Events/EventIndexTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
$response->assertStatus(200);
});

test('it only shows published and upcoming events', function () {
$visible = Event::factory()->create(['title' => 'Meetup Visível']);
test('it shows published events, past and upcoming, but not drafts', function () {
$upcoming = Event::factory()->create(['title' => 'Meetup Visível']);
Event::factory()->unpublished()->create(['title' => 'Rascunho Escondido']);
Event::factory()->past()->create(['title' => 'Evento Passado']);

$response = $this->get(route('events.index'));

$response->assertSee('Meetup Visível');
$response->assertSee('Evento Passado');
$response->assertDontSee('Rascunho Escondido');
$response->assertDontSee('Evento Passado');
});
Loading
Loading