Skip to content
Open
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
12 changes: 11 additions & 1 deletion app-modules/events/src/Event/Models/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;

/**
* @property string $id
Expand All @@ -33,11 +35,12 @@
* @property Carbon $updated_at
*/
#[Table(name: 'events')]
final class Event extends Model
final class Event extends Model implements HasMedia
{
/** @use HasFactory<EventFactory> */
use HasFactory;
use HasUuids;
use InteractsWithMedia;

protected $fillable = [
'slug',
Expand Down Expand Up @@ -72,6 +75,13 @@ public function checkInCodes(): HasMany
return $this->hasMany(CheckInCode::class);
}

public function registerMediaCollections(): void
{
$this->addMediaCollection('cover')
->singleFile()
->useDisk('public');
}

public function isPast(): bool
{
return $this->ends_at->isPast();
Expand Down
12 changes: 9 additions & 3 deletions app-modules/events/src/EventsServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
use He4rt\Events\CheckIn\Listeners\HandleBotCheckIn;
use He4rt\Events\Console\Commands\ClosePendingEventsCommand;
use He4rt\Events\Enrollment\Events\EnrollmentConfirmed;
use He4rt\Events\Event\Models\Event;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\Facades\Event;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Facades\Event as EventFacade;
use Illuminate\Support\ServiceProvider;

class EventsServiceProvider extends ServiceProvider
Expand All @@ -25,8 +27,12 @@ public function boot(): void
$this->loadViewsFrom(__DIR__.'/../resources/views', 'events');
$this->loadTranslationsFrom(__DIR__.'/../lang', 'events');

Event::listen(EnrollmentConfirmed::class, GenerateQrTokenOnConfirmed::class);
Event::listen(CheckInRequested::class, HandleBotCheckIn::class);
Relation::morphMap([
'event' => Event::class,
]);

EventFacade::listen(EnrollmentConfirmed::class, GenerateQrTokenOnConfirmed::class);
EventFacade::listen(CheckInRequested::class, HandleBotCheckIn::class);

if ($this->app->runningInConsole()) {
$this->app->make(Schedule::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"size" => "lg",
"animation" => "fade-up",
"keywords" => [],
"titleTag" => "h1",
])

@php
Expand Down Expand Up @@ -64,7 +65,7 @@ class="hp-headline-badge"

<div class="hp-headline-content space-y-4">
@isset($title)
<h1
<{{ $titleTag }}
{{ $title->attributes->class(["hp-headline-title", "delay-100" => $animate]) }}
@if ($animate)
x-show="shown"
Expand All @@ -85,7 +86,7 @@ class="hp-headline-badge"
{{ " " }}
@endunless
@endforeach
</h1>
</{{ $titleTag }}>
@endisset

@isset($subtitle)
Expand Down
1 change: 1 addition & 0 deletions app-modules/panel-admin/lang/en/events.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
'type' => 'Type',
'location' => 'Location',
'description' => 'Description',
'cover' => 'Cover',
'starts_at' => 'Starts At',
'ends_at' => 'Ends At',
'status' => 'Status',
Expand Down
1 change: 1 addition & 0 deletions app-modules/panel-admin/lang/pt_BR/events.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
'type' => 'Tipo',
'location' => 'Local',
'description' => 'Descrição',
'cover' => 'Capa',
'starts_at' => 'Início',
'ends_at' => 'Término',
'status' => 'Status',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\SpatieMediaLibraryFileUpload;
use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
Expand Down Expand Up @@ -61,6 +62,13 @@ public static function configure(Schema $schema): Schema
->nullable()
->columnSpanFull(),

SpatieMediaLibraryFileUpload::make('cover')
->label(__('panel-admin::events.columns.cover'))
->collection('cover')
->image()
->imageEditor()
->columnSpanFull(),

DateTimePicker::make('starts_at')
->label(__('panel-admin::events.columns.starts_at'))
->required(),
Expand Down
1 change: 1 addition & 0 deletions app-modules/portal/resources/views/homepage.blade.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<div>
<livewire:hero-section />
<livewire:upcoming-events-section />
{{-- <x-portal::sections.purpose />--}}
{{-- <x-portal::sections.projects />--}}
{{-- <x-portal::sections.articles />--}}
Expand Down
254 changes: 254 additions & 0 deletions app-modules/portal/resources/views/sections/upcoming-events.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
@php
$events = $this->upcomingEvents;
@endphp

<div>
<section class="hp-section" id="agenda">
@if ($events->isNotEmpty())
<script type="application/ld+json">
{!! json_encode($this->schemaOrg, JSON_HEX_TAG | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) !!}
</script>
@endif

<div class="hp-container">
<x-he4rt::headline size="md" align="center" :keywords="['eventos', 'He4rt']" title-tag="h2">
<x-slot:title>Próximos eventos da comunidade He4rt</x-slot:title>

<x-slot:description>
Eventos semanais e gratuitos de tecnologia para todos os níveis, de quem está começando agora a quem já trabalha na área. Participe de encontros, aulas, presencialmente ou online. Aprenda a programar, tire suas dúvidas e conecte-se com uma comunidade de desenvolvedores em crescimento.
</x-slot:description>
</x-he4rt::headline>

@if ($events->isEmpty())
<div class="mx-auto flex max-w-2xl flex-col items-center gap-4 rounded-2xl border border-outline-low/60 bg-elevation-surface px-6 py-12 text-center">
<div class="grid h-14 w-14 place-items-center rounded-full bg-primary/10 text-primary">
<x-filament::icon icon="heroicon-o-calendar-days" class="h-7 w-7" />
</div>
<p class="text-text-high text-lg font-bold tracking-tight">
Nenhum evento agendado no momento
</p>
<p class="text-text-medium max-w-md text-sm">
A comunidade He4rt está sempre criando novos eventos, aulas e encontros. Fique de olho no nosso
Discord para saber quando o próximo for anunciado.
</p>
<x-he4rt::button
:href="'https://discord.gg/he4rt'"
target="_blank"
variant="solid"
icon="heroicon-s-arrow-right"
class="mt-2"
>
Entrar no Discord
</x-he4rt::button>
</div>
@else
<div
class="events-carousel relative w-full max-w-6xl"
x-data="{
scrollable: false,
atStart: true,
atEnd: true,
dragging: false,
startX: 0,
startLeft: 0,
moved: false,
update() {
const t = $refs.track;
this.scrollable = t.scrollWidth > t.clientWidth + 2;
this.atStart = t.scrollLeft <= 2;
this.atEnd = t.scrollLeft + t.clientWidth >= t.scrollWidth - 2;
},
nudge(dir) {
const t = $refs.track;
const slide = t.querySelector('.events-slide');
const step = slide ? slide.offsetWidth + 24 : t.clientWidth * 0.8;
t.scrollBy({ left: dir * step, behavior: 'smooth' });
},
down(e) {
if (e.pointerType !== 'mouse') return;
e.preventDefault();
this.dragging = true;
this.moved = false;
this.startX = e.clientX;
this.startLeft = $refs.track.scrollLeft;
},
move(e) {
if (!this.dragging) return;
const dx = e.clientX - this.startX;
if (Math.abs(dx) > 4) this.moved = true;
$refs.track.scrollLeft = this.startLeft - dx;
},
up() {
this.dragging = false;
},
click(e) {
if (this.moved) {
e.preventDefault();
e.stopPropagation();
this.moved = false;
}
},
}"
x-init="update(); $nextTick(() => update())"
@resize.window="update()"
>
<button
type="button"
class="absolute top-1/2 left-0 z-10 grid h-10 w-10 -translate-y-1/2 place-items-center rounded-full border border-outline-low bg-elevation-surface/90 text-text-high shadow-lg backdrop-blur transition-all duration-300 hover:border-primary hover:text-primary sm:-left-5"
:class="!scrollable || atStart ? 'pointer-events-none scale-75 opacity-0' : 'opacity-100'"
@click="nudge(-1)"
aria-label="Eventos anteriores"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5">
<polyline points="15 18 9 12 15 6" />
</svg>
</button>

<div
class="flex items-stretch gap-6 overflow-x-auto px-1 py-3 scroll-auto cursor-grab snap-x snap-proximity active:cursor-grabbing [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
x-ref="track"
@scroll.passive="update()"
@pointerdown="down($event)"
@pointermove="move($event)"
@pointerup.window="up()"
@pointercancel.window="up()"
@click.capture="click($event)"
>
@foreach ($events as $item)
@php
$event = $item['event'];
$occurrence = $item['occurrence'];
$cover = $event->getFirstMedia('cover');
$offline = str_contains(mb_strtolower((string) $event->location), 'online') === false && $event->location !== null;
@endphp

<div class="events-slide shrink-0 snap-start basis-[clamp(280px,30%,400px)]">
<article
class="group relative flex h-full flex-col overflow-hidden rounded-2xl border border-outline-low/60 bg-elevation-surface transition-all duration-500 hover:-translate-y-1 hover:border-primary/40 hover:shadow-xl"
x-data="{
shareData: @js([
'title' => $event->title,
'text' => implode("\n", array_filter([
$event->title,
$event->description,
$event->location,
$occurrence->translatedFormat('d M Y H:i'),
])) . "\n\nSaiba mais -> " . url('/'),
]),
copied: false,
share() {
if (navigator.share) {
navigator.share(this.shareData).catch(() => {});
} else if (navigator.clipboard) {
navigator.clipboard.writeText(this.shareData.text);
this.copied = true;
setTimeout(() => (this.copied = false), 2000);
}
},
}"
>
<div class="relative h-40 shrink-0 overflow-hidden sm:h-44">
<div class="absolute inset-0 h-full w-full">
@if ($cover)
<img
src="{{ $cover->getUrl() }}"
alt="Capa do evento {{ $event->title }}"
@if ($cover->width) width="{{ $cover->width }}" @endif
@if ($cover->height) height="{{ $cover->height }}" @endif
loading="lazy"
fetchpriority="low"
class="h-full w-full object-cover"
/>
@else
<div
class="from-primary/12 to-primary/4 flex h-full w-full items-center justify-center bg-gradient-to-br"
aria-hidden="true"
>
<img
src="{{ asset('images/landingLogo.svg') }}"
alt=""
class="h-3/5 w-auto opacity-70"
/>
</div>
@endif
</div>

<div
class="pointer-events-none absolute inset-0 bg-gradient-to-t from-elevation-surface via-elevation-surface/20 to-black/25"
aria-hidden="true"
></div>

<span
class="absolute left-4 top-4 rounded-full bg-primary px-3 py-1 text-[10px] font-bold tracking-wide text-white uppercase"
>
{{ $offline ? 'Presencial' : 'Online' }}
</span>

<button
type="button"
class="absolute top-4 right-4 z-10 grid h-9 w-9 place-items-center rounded-full bg-black/40 text-white backdrop-blur transition-colors hover:bg-primary"
@click="share()"
:title="copied ? 'Link copiado!' : 'Compartilhar evento'"
aria-label="Compartilhar evento"
>
<x-filament::icon icon="heroicon-s-share" class="h-4 w-4" x-show="!copied" />
<x-filament::icon icon="heroicon-s-check" class="h-4 w-4" x-cloak x-show="copied" />
</button>

<time
class="text-text-high absolute bottom-4 left-4 flex items-center gap-1.5 text-sm font-semibold"
datetime="{{ $occurrence->toIso8601String() }}"
>
<x-filament::icon icon="heroicon-o-calendar-days" class="text-primary h-4 w-4" />
{{ $occurrence->format('d') }} {{ strtoupper($occurrence->translatedFormat('M')) }}
• {{ $occurrence->format('H:i') }}
</time>
</div>

<div class="flex flex-1 flex-col gap-2 p-6 pt-4">
<h3 class="text-text-high text-lg font-bold tracking-tight">{{ $event->title }}</h3>

@if ($event->description)
<p class="text-text-medium text-sm">{{ $event->description }}</p>
@endif
</div>

<div class="mt-auto flex items-center justify-between gap-3 border-t border-outline-low/60 px-6 py-4">
<div class="text-text-medium min-w-0">
@if ($event->location)
<p class="truncate text-xs">{{ $event->location }}</p>
@endif
</div>

<x-he4rt::button
:href="'https://discord.gg/he4rt'"
target="_blank"
variant="outline"
size="sm"
icon="heroicon-s-chevron-right"
class="shrink-0"
>
Participar
</x-he4rt::button>
</div>
</article>
</div>
@endforeach
</div>

<button
type="button"
class="absolute top-1/2 right-0 z-10 grid h-10 w-10 -translate-y-1/2 place-items-center rounded-full border border-outline-low bg-elevation-surface/90 text-text-high shadow-lg backdrop-blur transition-all duration-300 hover:border-primary hover:text-primary sm:-right-5"
:class="!scrollable || atEnd ? 'pointer-events-none scale-75 opacity-0' : 'opacity-100'"
@click="nudge(1)"
aria-label="Próximos eventos"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5">
<polyline points="9 18 15 12 9 6" />
</svg>
</button>
</div>
@endif
</div>
</section>
</div>
Loading