Skip to content

Commit f3ab4cc

Browse files
feat: landing page
1 parent 6cf8412 commit f3ab4cc

14 files changed

Lines changed: 273 additions & 34 deletions

File tree

assets/css/app.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
@theme {
1414
--color-primary: #ee7749;
15+
--color-app-bg: #F9F9F9;
1516
}
1617

1718

lib/yearbook/entries.ex

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,36 @@ defmodule Yearbook.Entries do
8686
}
8787
end
8888

89+
@doc """
90+
Returns the list of accepted entries.
91+
92+
## Options
93+
* `:filters` - A map of filters to apply to the query.
94+
Ex: %{"masters" => "true", "year" => "2026"}
95+
"""
96+
def list_accepted_entries(filters \\ %{}) do
97+
query = from e in Entry, where: e.status == :accepted
98+
99+
query =
100+
case Map.get(filters, "masters") do
101+
"true" -> from e in query, where: e.masters == true
102+
"false" -> from e in query, where: e.masters == false
103+
_ -> query
104+
end
105+
106+
query =
107+
case Map.get(filters, "year") do
108+
year_str when year_str in [nil, ""] ->
109+
query
110+
111+
year_str ->
112+
year_int = String.to_integer(year_str)
113+
from e in query, where: e.year == ^year_int
114+
end
115+
116+
Repo.all(from e in query, order_by: [asc: e.name])
117+
end
118+
89119
@doc """
90120
Gets a single entry.
91121

lib/yearbook/entries/entry.ex

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,28 @@ defmodule Yearbook.Entries.Entry do
66
use Ecto.Schema
77
import Ecto.Changeset
88

9-
@required_fields ~w(photo name text)a
10-
@optional_fields ~w(status)a
9+
@required_fields ~w(photo name text year)a
10+
@optional_fields ~w(status masters)a
1111
@primary_key {:id, :binary_id, autogenerate: true}
1212
@foreign_key_type :binary_id
1313
schema "entries" do
1414
field :photo, :string
1515
field :name, :string
1616
field :text, :string
1717
field :status, Ecto.Enum, values: [:accepted, :pending, :denied], default: :pending
18+
field :masters, :boolean, default: false
19+
field :year, :integer
1820

1921
timestamps(type: :utc_datetime)
2022
end
2123

2224
@doc false
2325
def changeset(entry, attrs) do
26+
current_year = Date.utc_today().year
27+
2428
entry
2529
|> cast(attrs, @required_fields ++ @optional_fields)
2630
|> validate_required(@required_fields)
31+
|> validate_number(:year, greater_than: 2020, less_than_or_equal_to: current_year)
2732
end
2833
end

lib/yearbook_web/components/entry_card.ex

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,25 @@ defmodule YearbookWeb.Components.EntryCard do
1111

1212
def entry_card(assigns) do
1313
~H"""
14-
<div class="flex flex-col items-center gap-2 p-2 overflow-auto bg-white rounded-xl shadow-md w-full min-h-56 sm:min-h-72 lg:min-h-96">
15-
<div class="w-full shrink-0 overflow-hidden aspect-square sm:aspect-4/3">
14+
<div class="flex flex-col items-center gap-3 p-4 bg-white rounded-2xl border border-gray-100 w-full h-full transition-all">
15+
<div class="w-full aspect-square overflow-hidden rounded-xl border border-gray-50">
1616
<%= if @src do %>
17-
<img
18-
src={@src}
19-
alt={@name}
20-
class="w-full h-full object-cover rounded-md"
21-
/>
17+
<img src={@src} alt={@name} class="w-full h-full object-cover" />
2218
<% else %>
23-
<div class="flex items-center justify-center w-full h-full rounded-xl bg-gray-300 text-white font-bold text-2xl">
19+
<div class="flex items-center justify-center w-full h-full bg-gray-100 text-gray-400 font-bold text-xl">
2420
{get_initials(@name)}
2521
</div>
2622
<% end %>
2723
</div>
28-
<h1 class="text-center w-full wrap-break-word font-bold px-2 text-md sm:text-lg lg:text-xl">
29-
{@name}
30-
</h1>
31-
<p class="flex-1 place-items-center p-2 text-sm sm:text-md lg:text-lg">{@text}</p>
24+
25+
<div class="text-center w-full">
26+
<h1 class="font-bold text-gray-900 text-sm md:text-base leading-tight">
27+
{@name}
28+
</h1>
29+
<p class="italic text-[11px] md:text-xs text-gray-500 mt-2 line-clamp-3 leading-relaxed">
30+
"{@text}"
31+
</p>
32+
</div>
3233
</div>
3334
"""
3435
end

lib/yearbook_web/components/layouts/landing.html.heex

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,16 @@
3232
})();
3333
</script>
3434
</head>
35-
<body class="bg-gray-100">
35+
<body class="bg-app-bg">
3636
<div class="relative min-h-screen w-full overflow-hidden">
37-
<img
38-
src="/images/0.png"
39-
class="absolute inset-0 h-full w-full object-cover"
40-
alt="background"
41-
/>
42-
4337
<div class="relative z-10 flex flex-col min-h-screen">
4438
<.navbar current_scope={assigns[:current_scope]} />
4539

4640
<div class="px-4 pt-4 md:px-8">
4741
<.flash_group flash={@flash} />
4842
</div>
4943

50-
<div class="flex grow md:items-center md:justify-center">
44+
<div class="flex grow md:items-start md:justify-center">
5145
{@inner_content}
5246
</div>
5347
<.footer />

lib/yearbook_web/components/layouts/root.html.heex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
})();
3333
</script>
3434
</head>
35-
<body class="bg-gray-100">
35+
<body class="bg-app-bg">
3636
{@inner_content}
3737
</body>
3838
</html>
Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,48 @@
11
defmodule YearbookWeb.HomeLive.Index do
2-
use YearbookWeb, :landing_view
3-
42
@moduledoc """
53
Landing Page
64
"""
5+
use YearbookWeb, :landing_view
6+
alias Yearbook.Entries
7+
import YearbookWeb.Components.EntryCard
8+
79
def mount(_params, _session, socket) do
10+
entries = Entries.list_accepted_entries(%{"masters" => "", "year" => ""})
11+
812
{:ok,
913
socket
10-
|> assign(page_title: "Home | Yearbook")}
14+
|> assign(page_title: "Home | Yearbook")
15+
|> assign(entries: entries)
16+
|> assign(total_entries: Enum.count(entries))
17+
|> assign(filters: %{"masters" => "", "year" => ""})}
18+
end
19+
20+
def handle_params(_params, _url, socket) do
21+
{:noreply, socket}
22+
end
23+
24+
def handle_event("filter_changed", params, socket) do
25+
entries = Entries.list_accepted_entries(params)
26+
27+
{:noreply,
28+
socket
29+
|> assign(entries: entries)
30+
|> assign(total_entries: Enum.count(entries))
31+
|> assign(filters: params)}
32+
end
33+
34+
defp list_academic_years do
35+
current_year = Date.utc_today().year
36+
37+
years =
38+
2020..current_year
39+
|> Enum.map(fn y -> {format_academic_year(y), "#{y}"} end)
40+
|> Enum.reverse()
41+
42+
[{"Todos os Anos", ""} | years]
43+
end
44+
45+
defp format_academic_year(year) do
46+
"#{year - 1}/#{rem(year, 100)}"
1147
end
1248
end
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<div class="p-4 md:p-10 w-full min-h-screen bg-app-bg">
2+
<div class="max-w-7xl mx-auto">
3+
<div class="mb-12">
4+
<div class="flex items-center gap-4">
5+
<h1 class="text-4xl md:text-5xl font-bold text-[#1A1A1A] tracking-tight">
6+
Yearbook
7+
</h1>
8+
9+
<div class="relative inline-block text-left mt-2" id="year-menu-container">
10+
<button
11+
type="button"
12+
phx-click={
13+
Phoenix.LiveView.JS.toggle(
14+
to: "#year-dropdown",
15+
in: "fade-in-scale",
16+
out: "fade-out-scale"
17+
)
18+
}
19+
phx-click-away={Phoenix.LiveView.JS.hide(to: "#year-dropdown")}
20+
class="flex items-center gap-2 bg-orange-50 text-orange-600 border border-orange-100 px-4 py-1.5 rounded-full text-sm font-bold focus:outline-none hover:bg-orange-100 transition-all shadow-sm"
21+
>
22+
<%= case @filters["year"] do %>
23+
<% "" -> %>
24+
<span>Ano Letivo</span>
25+
<% year -> %>
26+
<span>{format_academic_year(String.to_integer(year))}</span>
27+
<% end %>
28+
<.icon name="hero-chevron-down" class="size-4" />
29+
</button>
30+
31+
<div
32+
id="year-dropdown"
33+
class="hidden absolute left-0 mt-2 w-48 origin-top-left rounded-2xl bg-white shadow-xl ring-1 ring-black ring-opacity-5 z-50 overflow-hidden border border-gray-100"
34+
>
35+
<div class="py-2">
36+
<%= for {label, value} <- list_academic_years() do %>
37+
<button
38+
phx-click={
39+
Phoenix.LiveView.JS.push("filter_changed",
40+
value: %{year: value, masters: @filters["masters"]}
41+
)
42+
|> Phoenix.LiveView.JS.hide(to: "#year-dropdown")
43+
}
44+
class={"block w-full text-left px-5 py-2.5 text-sm transition-all #{if @filters["year"] == value, do: "bg-orange-50 text-orange-600 font-bold", else: "text-gray-600 hover:bg-gray-50 hover:text-orange-500"}"}
45+
>
46+
<div class="flex items-center justify-between">
47+
<span>{label}</span>
48+
<%= if @filters["year"] == value do %>
49+
<div class="size-1.5 rounded-full bg-orange-500"></div>
50+
<% end %>
51+
</div>
52+
</button>
53+
<% end %>
54+
</div>
55+
</div>
56+
</div>
57+
</div>
58+
59+
<div class="flex items-center gap-8 mt-8">
60+
<div class="flex gap-8">
61+
<button
62+
phx-click="filter_changed"
63+
phx-value-masters=""
64+
phx-value-year={@filters["year"]}
65+
class="pb-2 relative group focus:outline-none"
66+
>
67+
<span class={"text-sm font-bold tracking-wide uppercase #{if @filters["masters"] == "", do: "text-orange-600", else: "text-gray-400 hover:text-gray-600"}"}>
68+
Todos
69+
</span>
70+
<%= if @filters["masters"] == "" do %>
71+
<div class="absolute bottom-0 left-0 w-full h-0.5 bg-orange-600"></div>
72+
<% end %>
73+
</button>
74+
75+
<button
76+
phx-click="filter_changed"
77+
phx-value-masters="false"
78+
phx-value-year={@filters["year"]}
79+
class="pb-2 relative group focus:outline-none"
80+
>
81+
<span class={"text-sm font-bold tracking-wide uppercase #{if @filters["masters"] == "false", do: "text-orange-600", else: "text-gray-400 hover:text-gray-600"}"}>
82+
Licenciatura
83+
</span>
84+
<%= if @filters["masters"] == "false" do %>
85+
<div class="absolute bottom-0 left-0 w-full h-0.5 bg-orange-600"></div>
86+
<% end %>
87+
</button>
88+
89+
<button
90+
phx-click="filter_changed"
91+
phx-value-masters="true"
92+
phx-value-year={@filters["year"]}
93+
class="pb-2 relative group focus:outline-none"
94+
>
95+
<span class={"text-sm font-bold tracking-wide uppercase #{if @filters["masters"] == "true", do: "text-orange-600", else: "text-gray-400 hover:text-gray-600"}"}>
96+
Mestrado
97+
</span>
98+
<%= if @filters["masters"] == "true" do %>
99+
<div class="absolute bottom-0 left-0 w-full h-0.5 bg-orange-600"></div>
100+
<% end %>
101+
</button>
102+
</div>
103+
104+
<%= if assigns[:current_scope] && @current_scope.user do %>
105+
<.link
106+
patch={~p"/entries/new"}
107+
class="flex items-center justify-center gap-2 px-3 py-1.5 bg-white border border-gray-200 rounded-lg text-xs font-bold uppercase tracking-wider text-gray-600 hover:bg-gray-50 hover:border-orange-200 hover:text-orange-600 transition-all shadow-sm min-w-[40px]"
108+
>
109+
<.icon name="hero-plus" class="size-4 " />
110+
111+
<span class="hidden md:inline">Adicionar</span>
112+
</.link>
113+
<% end %>
114+
</div>
115+
116+
<p class="mt-6 text-gray-500 max-w-2xl text-sm leading-relaxed">
117+
<%= case {@filters["masters"], @filters["year"]} do %>
118+
<% {"", ""} -> %>
119+
Todos os alunos registados no Yearbook.
120+
<% {"true", ""} -> %>
121+
Alunos do Mestrado de todos os anos letivos.
122+
<% {"false", ""} -> %>
123+
Alunos da Licenciatura de todos os anos letivos.
124+
<% {masters, year} -> %>
125+
Alunos {if masters == "true", do: "do Mestrado", else: "da Licenciatura"} do Ano Letivo {format_academic_year(
126+
String.to_integer(year)
127+
)}.
128+
<% end %>
129+
</p>
130+
</div>
131+
132+
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-x-8 gap-y-12">
133+
<%= for entry <- @entries do %>
134+
<.entry_card name={entry.name} text={entry.text} src={entry.photo} />
135+
<% end %>
136+
</div>
137+
</div>
138+
</div>

lib/yearbook_web/router.ex

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@ defmodule YearbookWeb.Router do
2424
end
2525

2626
scope "/", YearbookWeb do
27-
pipe_through :browser
27+
pipe_through [:browser]
2828

29-
live_session :navbar,
30-
on_mount: [{YearbookWeb.UserAuth, :mount_current_scope}] do
29+
live_session :public_home, on_mount: [{YearbookWeb.UserAuth, :mount_current_scope}] do
3130
live "/", HomeLive.Index, :index
3231
end
32+
33+
live_session :private_home, on_mount: [{YearbookWeb.UserAuth, :require_authenticated}] do
34+
live "/entries/new", HomeLive.Index, :new
35+
end
3336
end
3437

3538
scope "/backoffice", YearbookWeb do
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
defmodule Yearbook.Repo.Migrations.AddMastersToEntries do
2+
use Ecto.Migration
3+
4+
def change do
5+
alter table(:entries) do
6+
add :masters, :boolean, default: false, null: false
7+
end
8+
end
9+
end

0 commit comments

Comments
 (0)