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
10 changes: 10 additions & 0 deletions lib/dev_round/events.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ defmodule DevRound.Events do
|> Repo.all()
end

def list_registered_events_for_user(user_id) do
from(e in Event,
join: ea in assoc(e, :events_attendees),
where: ea.user_id == ^user_id and e.published,
order_by: [asc: e.begin]
)
|> Repo.all()
|> Repo.preload([:langs, sessions: from(s in EventSession, order_by: s.begin)])
end

def get_event_archival_datetime_utc do
tz = Application.get_env(:dev_round, :time_zone)

Expand Down
34 changes: 34 additions & 0 deletions lib/dev_round/formats.ex
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,40 @@ defmodule DevRound.Formats do
"#{format_time(dt1)} – #{format_time(dt2)}"
end

@doc """
Formats a datetime range into a compact string.

The formatting adapts based on whether the dates are the same and match the current date:
- Today and same day: Shows time range (e.g., "16:00 – 18:00")
- Otherwise: Shows full datetime range (see `format_datetime_range/2`)

## Examples

iex> format_datetime_range_compact(~U[2026-03-24 16:00:00Z], ~U[2026-03-24 18:00:00Z])
"16:00 – 18:00"

iex> format_datetime_range_compact(~U[2025-03-14 16:00:00Z], ~U[2025-03-15 18:00:00Z])
"14.03.2025 16:00 – 15.03.2025 18:00"

## Parameters

- `dt1`: The start datetime
- `dt2`: The end datetime

## Returns

A formatted string representing the date/time range or just the time range.
"""
def format_datetime_range_compact(dt1, dt2) do
{:ok, now} = DateTime.now(dt1.time_zone)

if Date.compare(now, dt1) == :eq and Date.compare(dt1, dt2) == :eq do
format_time_range(dt1, dt2)
else
format_datetime_range(dt1, dt2)
end
end

@doc """
Generates an avatar placeholder from a user's full name.

Expand Down
14 changes: 14 additions & 0 deletions lib/dev_round/hosting.ex
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,20 @@ defmodule DevRound.Hosting do
|> Repo.preload(members: {member_query, [:user, :langs]})
end

@doc """
Lists all teams for a given user across multiple sessions.
Returns a map of session_id => team
"""
def list_teams_for_user_in_sessions(user_id, session_ids) do
from(t in Team,
join: m in assoc(t, :members),
where: t.session_id in ^session_ids and m.user_id == ^user_id
)
|> Repo.all()
|> Repo.preload([:lang, members: [:user, :langs]])
|> Enum.into(%{}, fn team -> {team.session_id, team} end)
end

def build_teams_for_session(%EventSession{} = session, attendees, team_names) do
attendees = filter_checked(attendees)
{:ok, []} = validate_team_generation_constraints(attendees, team_names)
Expand Down
132 changes: 132 additions & 0 deletions lib/dev_round_web/components/event_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,64 @@ defmodule DevRoundWeb.EventComponents do

use Phoenix.Component
use DevRoundWeb, :verified_routes
import DevRoundWeb.CoreComponents
alias DevRound.Events.EventSession
alias DevRound.Hosting.Team

attr :events, :list, required: true
attr :title, :string, required: true
attr :accent_class, :string, required: true
slot :placeholder

def event_grid_listing(assigns) do
~H"""
<div class="mb-12">
<div class="flex items-center gap-3 mb-6">
<h2 class="text-2xl font-mono font-semibold text-base-content">{@title}</h2>
<div class={"badge badge-#{@accent_class} badge-lg"}>
{length(@events)}
</div>
</div>

<%= if @events == [] do %>
{render_slot(@placeholder)}
<% else %>
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<.link
:for={event <- @events}
:key={event.id}
patch={~p"/events/#{event}"}
class="card card-sm bg-base-300 shadow-md transition-shadow duration-200 border border-base-content/10 hover:border-primary/50"
>
<div class="card-body">
<h3 class="card-title text-lg font-mono font-semibold text-base-content">
{event.title}
</h3>

<p class="mt-3 text-sm">{event.teaser}</p>

<div class="flex flex-col gap-2 mt-3">
<div class="flex items-center gap-2 text-sm text-base-content/70">
<.icon name="hero-calendar" class="w-4 h-4" />
{DevRound.Formats.format_datetime_range(
event.begin |> DateTime.shift_zone!(DevRound.Formats.time_zone()),
event.end |> DateTime.shift_zone!(DevRound.Formats.time_zone())
)}
</div>

<div class="flex items-center gap-2 text-sm text-base-content/70">
<.icon name="hero-map-pin" class="w-4 h-4" />
{event.location}
</div>
</div>
</div>
</.link>
</div>
<% end %>
</div>
"""
end

attr :team, Team, required: true
attr :show_member_experience_level, :boolean, required: true
attr :show_member_langs, :boolean, required: true
Expand Down Expand Up @@ -64,4 +120,80 @@ defmodule DevRoundWeb.EventComponents do
</div>
"""
end

attr :session, EventSession, required: true
attr :class, :string, default: nil

def session(assigns) do
~H"""
<div class={["card bg-base-300", @class]}>
<div class="card-body p-2 gap-2">
<h2 class="card-title flex gap-3 ">
<span class="grow text-lg font-mono font-semibold text-base-content">
{@session.title}
</span>
<.live_badge :if={@session.live} />
</h2>
<div class="card grid lg:grid-cols-2 items-center gap-4">
<div class="">
<div class="text-sm text-base-content/70">
Begin & End
</div>
<div class="text-lg font-mono">
{DevRound.Formats.format_datetime_range_compact(@session.begin, @session.end)}
</div>
</div>

<%= if @session.live do %>
<div class="">
<div class="text-sm text-base-content/70">
Time Remaining
</div>
<.live_component
module={DevRoundWeb.EventSessionCountdownLive}
id={"countdown-#{@session.id}"}
event_session={@session}
class="text-lg font-mono"
style={nil}
/>
</div>
<% end %>
</div>
<div class="text-base-content/60"></div>
</div>
</div>
"""
end

def live_badge(assigns) do
~H"""
<div class="badge badge-info flex items-center gap-2">
<span class="relative flex h-3 w-3">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-error opacity-75">
</span>
<span class="relative inline-flex rounded-full h-3 w-3 bg-error"></span>
</span>
<span class="font-semibold uppercase mt-px">Live</span>
</div>
"""
end

attr :title, :string, required: false
slot :inner_block

def content_placeholder(assigns) do
~H"""
<div class="card bg-base-200 shadow-sm border border-base-content/10">
<div class="card-body text-center py-12">
<div class="mb-4">
<img src={~p"/images/broken-heart.svg"} class="inline opacity-50 w-[128px]" />
</div>
<h3 class="text-xl font-medium text-base-content/50 mb-2">{@title}</h3>
<p class="text-base-content/50">
{render_slot(@inner_block)}
</p>
</div>
</div>
"""
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ defmodule DevRoundWeb.EventSessionCountdownLive do
@impl true
def render(assigns) do
~H"""
<div class={@class} style={@style}>
<div class={["pt-[2px]", @class]} style={@style}>
<%= if @time_remaining do %>
<div class="countdown">
<%= if @time_remaining.day_digits > 0 do %>
Expand Down
21 changes: 5 additions & 16 deletions lib/dev_round_web/components/event_session_teams_slide_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -73,21 +73,10 @@ defmodule DevRoundWeb.EventSessionTeamsSlideLive do
end

defp assign_dates(socket, event_session) do
{:ok, now} = DateTime.now(Formats.time_zone())

if Date.compare(now, event_session.begin_local) == :eq and
Date.compare(event_session.begin_local, event_session.end_local) == :eq do
socket
|> assign(
:time,
Formats.format_time_range(event_session.begin_local, event_session.end_local)
)
else
socket
|> assign(
:time,
Formats.format_datetime_range(event_session.begin_local, event_session.end_local)
)
end
assign(
socket,
:time,
Formats.format_datetime_range_compact(event_session.begin, event_session.end)
)
end
end
5 changes: 5 additions & 0 deletions lib/dev_round_web/components/layouts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ defmodule DevRoundWeb.Layouts do
/>
</:trigger>
<:menu class="w-52 p-2">
<li>
<.link href={~p"/user/events"}>
Your Events
</.link>
</li>
<li>
<.link href={~p"/users/settings"}>
Settings
Expand Down
49 changes: 0 additions & 49 deletions lib/dev_round_web/live/event_live/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,53 +14,4 @@ defmodule DevRoundWeb.EventLive.Index do
def handle_params(_params, _url, socket) do
{:noreply, socket}
end

def event_list(assigns) do
~H"""
<div class="mb-12">
<div class="flex items-center gap-3 mb-6">
<div class={"badge badge-#{@accent_class} badge-lg"}>
{length(@events)}
</div>
<h2 class="text-2xl font-mono font-semibold text-base-content">{@title}</h2>
</div>

<%= if @events == [] do %>
{render_slot(@placeholder)}
<% else %>
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<.link
:for={event <- @events}
:key={event.id}
patch={~p"/events/#{event}"}
class="card card-sm bg-base-300 shadow-md transition-shadow duration-200 border border-base-300 hover:border-primary/50"
>
<div class="card-body">
<h3 class="card-title text-lg font-mono font-semibold text-base-content">
{event.title}
</h3>

<p class="mt-3 text-sm">{event.teaser}</p>

<div class="flex flex-col gap-2 mt-3">
<div class="flex items-center gap-2 text-sm text-base-content/70">
<.icon name="hero-calendar" class="w-4 h-4" />
{DevRound.Formats.format_datetime_range(
event.begin |> DateTime.shift_zone!(DevRound.Formats.time_zone()),
event.end |> DateTime.shift_zone!(DevRound.Formats.time_zone())
)}
</div>

<div class="flex items-center gap-2 text-sm text-base-content/70">
<.icon name="hero-map-pin" class="w-4 h-4" />
{event.location}
</div>
</div>
</div>
</.link>
</div>
<% end %>
</div>
"""
end
end
12 changes: 8 additions & 4 deletions lib/dev_round_web/live/event_live/index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,25 @@
<:subtitle>Discover current and archived events</:subtitle>
</.header>

<.event_list events={@current_events} title="Current Events" accent_class="primary">
<DevRoundWeb.EventComponents.event_grid_listing
events={@current_events}
title="Current Events"
accent_class="primary"
>
<:placeholder>
<div class="card bg-base-200 shadow-sm">
<div class="card-body text-center py-12">
<div class="mb-4">
<img src={~p"/images/broken-heart.svg"} class="inline opacity-70 w-[128px]" />
</div>
<h3 class="text-xl font-medium text-base-content mb-2">No upcoming events</h3>
<h3 class="text-xl font-medium text-base-content mb-2">No current events</h3>
<p class="text-base-content/70">Check back soon for new events!</p>
</div>
</div>
</:placeholder>
</.event_list>
</DevRoundWeb.EventComponents.event_grid_listing>

<.event_list
<DevRoundWeb.EventComponents.event_grid_listing
:if={not Enum.empty?(@archived_events)}
events={@archived_events}
title="Archived Events"
Expand Down
2 changes: 1 addition & 1 deletion lib/dev_round_web/live/hosting_session_live/show.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
</.toolbar>

<div class="card grid lg:grid-cols-3 items-center gap-4 bg-base-300">
<div class="grid gap-1 p-2">
<div class="p-2">
<div class="text-sm text-base-content/70">
Scheduled Begin
</div>
Expand Down
Loading
Loading