From 6ee1202357853737c4fe75e02843c89505798c44 Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Sat, 28 Sep 2024 18:56:59 +0100 Subject: [PATCH 01/16] feat: block routes based on event date/time --- .env.dev.sample | 3 ++ .gitignore | 3 +- config/runtime.exs | 8 ++++ lib/safira_web/controllers/error_html.ex | 9 +--- .../controllers/error_html/404.html.heex | 1 + lib/safira_web/controllers/page_controller.ex | 11 +++++ .../controllers/page_html/countdown.html.heex | 46 +++++++++++++++++++ lib/safira_web/plugs/event_roles.ex | 38 +++++++++++++++ lib/safira_web/router.ex | 8 ++-- mix.exs | 1 + mix.lock | 1 + priv/repo/seeds/accounts.exs | 2 +- 12 files changed, 118 insertions(+), 13 deletions(-) create mode 100644 lib/safira_web/controllers/error_html/404.html.heex create mode 100644 lib/safira_web/controllers/page_html/countdown.html.heex create mode 100644 lib/safira_web/plugs/event_roles.ex diff --git a/.env.dev.sample b/.env.dev.sample index ea57545f..7d9fd60c 100644 --- a/.env.dev.sample +++ b/.env.dev.sample @@ -5,3 +5,6 @@ DB_PORT=5432 DB_NAME=safira_dev HOST_URL=http://localhost:4000 ASSET_HOST=http://localhost:4000 +REGISTRATIONS_OPEN=true +BACKOFFICE_ENABLED_STAFF=true +START_TIME=2024-02-06T09:00:00Z \ No newline at end of file diff --git a/.gitignore b/.gitignore index b287e301..c37cce96 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ safira-*.tar npm-debug.log /assets/node_modules/ -.env.dev +.env.* +.env diff --git a/config/runtime.exs b/config/runtime.exs index a74bdb45..3a170a69 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -1,4 +1,5 @@ import Config +import Dotenvy # config/runtime.exs is executed for all environments, including # during releases. It is executed after compilation and before the @@ -20,6 +21,13 @@ if System.get_env("PHX_SERVER") do config :safira, SafiraWeb.Endpoint, server: true end +source([".env.dev", System.get_env()]) + +config :safira, SafiraWeb.Endpoint, + registrations_open: env!("REGISTRATIONS_OPEN", :boolean, true), + backoffice_enabled_staff: env!("BACKOFFICE_ENABLED_STAFF", :boolean, true), + start_time: DateTime.from_iso8601(env!("START_TIME", :string!)) |> elem(1) + if config_env() == :prod do database_url = System.get_env("DATABASE_URL") || diff --git a/lib/safira_web/controllers/error_html.ex b/lib/safira_web/controllers/error_html.ex index 7ce2beaf..53f90140 100644 --- a/lib/safira_web/controllers/error_html.ex +++ b/lib/safira_web/controllers/error_html.ex @@ -13,12 +13,5 @@ defmodule SafiraWeb.ErrorHTML do # * lib/safira_web/controllers/error_html/404.html.heex # * lib/safira_web/controllers/error_html/500.html.heex # - # embed_templates "error_html/*" - - # The default is to render a plain text page based on - # the template name. For example, "404.html" becomes - # "Not Found". - def render(template, _assigns) do - Phoenix.Controller.status_message_from_template(template) - end + embed_templates "error_html/*" end diff --git a/lib/safira_web/controllers/error_html/404.html.heex b/lib/safira_web/controllers/error_html/404.html.heex new file mode 100644 index 00000000..03332b4f --- /dev/null +++ b/lib/safira_web/controllers/error_html/404.html.heex @@ -0,0 +1 @@ +

Not Found

diff --git a/lib/safira_web/controllers/page_controller.ex b/lib/safira_web/controllers/page_controller.ex index b36b5dd0..f32d6a3f 100644 --- a/lib/safira_web/controllers/page_controller.ex +++ b/lib/safira_web/controllers/page_controller.ex @@ -6,4 +6,15 @@ defmodule SafiraWeb.PageController do # so skip the default app layout. render(conn, :home, layout: false) end + + def countdown(conn, _params) do + start_time = Application.fetch_env!(:safira, SafiraWeb.Endpoint)[:start_time] + + if(DateTime.compare(start_time, DateTime.utc_now()) == :lt) do + conn + |> redirect(to: ~p"/app") + else + render(conn |> assign(:start_time, start_time), :countdown, layout: false) + end + end end diff --git a/lib/safira_web/controllers/page_html/countdown.html.heex b/lib/safira_web/controllers/page_html/countdown.html.heex new file mode 100644 index 00000000..41ff6d8c --- /dev/null +++ b/lib/safira_web/controllers/page_html/countdown.html.heex @@ -0,0 +1,46 @@ +
+

Countdown Timer

+
+ + +
diff --git a/lib/safira_web/plugs/event_roles.ex b/lib/safira_web/plugs/event_roles.ex new file mode 100644 index 00000000..0019b039 --- /dev/null +++ b/lib/safira_web/plugs/event_roles.ex @@ -0,0 +1,38 @@ +defmodule SafiraWeb.EventRoles do + alias Phoenix.Router.NoRouteError + use SafiraWeb, :verified_routes + + import Plug.Conn + import Phoenix.Controller + + @doc """ + Used to check if registrations have opened, so users can register / log in + """ + def registrations_open(conn, _opts) do + if Application.fetch_env!(:safira, SafiraWeb.Endpoint)[:registrations_open] do + conn + else + raise %Ecto.NoResultsError{} + end + end + + def backoffice_enabled(conn, _opts) do + attendees_allowed = + is_not_in_future(Application.fetch_env!(:safira, SafiraWeb.Endpoint)[:start_time]) + + staff_allowed = Application.fetch_env!(:safira, SafiraWeb.Endpoint)[:backoffice_enabled_staff] + + if (conn.assigns.current_user.type == :attendee and attendees_allowed) or + (conn.assigns.current_user.type == :staff and staff_allowed) do + conn + else + conn + |> redirect(to: ~p"/countdown") + |> halt() + end + end + + defp is_not_in_future(time) do + DateTime.compare(time, DateTime.utc_now()) == :lt + end +end diff --git a/lib/safira_web/router.ex b/lib/safira_web/router.ex index 558e94d2..ae5321a5 100644 --- a/lib/safira_web/router.ex +++ b/lib/safira_web/router.ex @@ -3,6 +3,7 @@ defmodule SafiraWeb.Router do import SafiraWeb.UserAuth import SafiraWeb.UserRoles + import SafiraWeb.EventRoles pipeline :browser do plug :accepts, ["html"] @@ -22,6 +23,7 @@ defmodule SafiraWeb.Router do pipe_through :browser get "/", PageController, :home + get "/countdown", PageController, :countdown end # Other scopes may use custom stacks. @@ -49,7 +51,7 @@ defmodule SafiraWeb.Router do ## Authentication routes scope "/", SafiraWeb do - pipe_through [:browser, :redirect_if_user_is_authenticated] + pipe_through [:browser, :redirect_if_user_is_authenticated, :registrations_open] live_session :redirect_if_user_is_authenticated, on_mount: [{SafiraWeb.UserAuth, :redirect_if_user_is_authenticated}] do @@ -73,7 +75,7 @@ defmodule SafiraWeb.Router do live "/scanner", ScannerLive.Index, :index scope "/app", App do - pipe_through [:require_attendee_user] + pipe_through [:require_attendee_user, :backoffice_enabled] scope "/credential", CredentialLive do pipe_through [:require_no_credential] @@ -96,7 +98,7 @@ defmodule SafiraWeb.Router do end scope "/dashboard", Backoffice do - pipe_through :require_staff_user + pipe_through [:require_staff_user, :backoffice_enabled] scope "/attendees", AttendeeLive do live "/", Index, :index diff --git a/mix.exs b/mix.exs index 4a5c8a67..04c7e1ff 100644 --- a/mix.exs +++ b/mix.exs @@ -83,6 +83,7 @@ defmodule Safira.MixProject do {:jason, "~> 1.2"}, {:lua, "~> 0.0.14"}, {:timex, "~> 3.7.11"}, + {:dotenvy, "~> 0.8.0"}, # server {:bandit, "~> 1.2"}, diff --git a/mix.lock b/mix.lock index 9229f2c6..da1462e8 100644 --- a/mix.lock +++ b/mix.lock @@ -11,6 +11,7 @@ "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"}, + "dotenvy": {:hex, :dotenvy, "0.8.0", "777486ad485668317c56afc53a7cbcd74f43e4e34588ba8e95a73e15a360050e", [:mix], [], "hexpm", "1f535066282388cbd109743d337ac46ff0708195780d4b5778bb83491ab1b654"}, "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, "ecto_sql": {:hex, :ecto_sql, "3.11.3", "4eb7348ff8101fbc4e6bbc5a4404a24fecbe73a3372d16569526b0cf34ebc195", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e5f36e3d736b99c7fee3e631333b8394ade4bafe9d96d35669fca2d81c2be928"}, "elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"}, diff --git a/priv/repo/seeds/accounts.exs b/priv/repo/seeds/accounts.exs index fbcfc524..55e3849e 100644 --- a/priv/repo/seeds/accounts.exs +++ b/priv/repo/seeds/accounts.exs @@ -28,7 +28,7 @@ defmodule Safira.Repo.Seeds.Accounts do [] -> seed_credentials(credential_count, div(attendee_names |> length(), 2)) _ -> - Mix.shell().erroring("Found credentials, aborting seeding credentials.") + Mix.shell().error("Found credentials, aborting seeding credentials.") end end From c8f0879fefcdfef0fa72049c100d1dce3564718e Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Sun, 29 Sep 2024 11:45:18 +0100 Subject: [PATCH 02/16] fix: migrate to constants --- lib/safira_web/controllers/page_controller.ex | 11 -------- lib/safira_web/helpers.ex | 19 ++++++++++++++ lib/safira_web/live/auth/user_login_live.ex | 18 ++++++++----- .../waiting/countdown_live.ex} | 26 +++++++++++++++++-- lib/safira_web/plugs/event_roles.ex | 16 +++++++----- lib/safira_web/router.ex | 13 +++++++--- priv/repo/seeds.exs | 1 + priv/repo/seeds/constants.exs | 10 +++++++ 8 files changed, 86 insertions(+), 28 deletions(-) rename lib/safira_web/{controllers/page_html/countdown.html.heex => live/waiting/countdown_live.ex} (74%) create mode 100644 priv/repo/seeds/constants.exs diff --git a/lib/safira_web/controllers/page_controller.ex b/lib/safira_web/controllers/page_controller.ex index f32d6a3f..b36b5dd0 100644 --- a/lib/safira_web/controllers/page_controller.ex +++ b/lib/safira_web/controllers/page_controller.ex @@ -6,15 +6,4 @@ defmodule SafiraWeb.PageController do # so skip the default app layout. render(conn, :home, layout: false) end - - def countdown(conn, _params) do - start_time = Application.fetch_env!(:safira, SafiraWeb.Endpoint)[:start_time] - - if(DateTime.compare(start_time, DateTime.utc_now()) == :lt) do - conn - |> redirect(to: ~p"/app") - else - render(conn |> assign(:start_time, start_time), :countdown, layout: false) - end - end end diff --git a/lib/safira_web/helpers.ex b/lib/safira_web/helpers.ex index 7971c6bd..19553fae 100644 --- a/lib/safira_web/helpers.ex +++ b/lib/safira_web/helpers.ex @@ -3,6 +3,7 @@ defmodule SafiraWeb.Helpers do Helper functions for web views. """ alias Timex.Format.DateTime.Formatters.Relative + alias Safira.Constants require Timex.Translator @@ -161,4 +162,22 @@ defmodule SafiraWeb.Helpers do |> QRCodeEx.encode() |> QRCodeEx.svg(background_color: "#FFFFFF", color: "#04041C", width: 200) end + + def registrations_open? do + {:ok, registrations_open} = Constants.get("REGISTRATIONS_OPEN") + string_to_bool(registrations_open) + end + + def get_start_time! do + {:ok, start_str} = Constants.get("START_TIME") + {:ok, start_time, _} = DateTime.from_iso8601(start_str) + start_time + end + + defp string_to_bool(str) do + case String.downcase(str) do + "true" -> true + _ -> false + end + end end diff --git a/lib/safira_web/live/auth/user_login_live.ex b/lib/safira_web/live/auth/user_login_live.ex index 0e82478c..5187a4cf 100644 --- a/lib/safira_web/live/auth/user_login_live.ex +++ b/lib/safira_web/live/auth/user_login_live.ex @@ -1,6 +1,8 @@ defmodule SafiraWeb.UserLoginLive do use SafiraWeb, :live_view + alias Safira.Constants + def render(assigns) do ~H"""
@@ -11,11 +13,14 @@ defmodule SafiraWeb.UserLoginLive do <.header class="text-center"> Log in to account <:subtitle> - Don't have an account? - <.link navigate={~p"/users/register"} class="font-semibold text-primary hover:underline"> - Sign up - - for an account now. + <%= if @registrations_open do %> + + Don't have an account? + <.link navigate={~p"/users/register"} class="font-semibold text-primary hover:underline"> + Sign up + + for an account now. + <% end %> @@ -42,6 +47,7 @@ defmodule SafiraWeb.UserLoginLive do def mount(_params, _session, socket) do email = Phoenix.Flash.get(socket.assigns.flash, :email) form = to_form(%{"email" => email}, as: "user") - {:ok, assign(socket, form: form), temporary_assigns: [form: form]} + + {:ok, assign(socket, form: form) |> assign(registrations_open: registrations_open?()), temporary_assigns: [form: form]} end end diff --git a/lib/safira_web/controllers/page_html/countdown.html.heex b/lib/safira_web/live/waiting/countdown_live.ex similarity index 74% rename from lib/safira_web/controllers/page_html/countdown.html.heex rename to lib/safira_web/live/waiting/countdown_live.ex index 41ff6d8c..5259f84e 100644 --- a/lib/safira_web/controllers/page_html/countdown.html.heex +++ b/lib/safira_web/live/waiting/countdown_live.ex @@ -1,4 +1,11 @@ -
+defmodule SafiraWeb.Waiting.CountdownLive do + use SafiraWeb, :live_view + + alias SafiraWeb.Helpers + + def render(assigns) do + ~H""" +

Countdown Timer

@@ -38,9 +45,24 @@ countdownElement.innerHTML = "Time Remaining: " + distance_str; } else { countdownElement.innerHTML = "Time's up!"; - clearInterval(countdownInterval); + clearInterval(countdownInterval); window.location.reload(); } }, 1000);
+ + """ + end + + def mount(_params, _session, socket) do + start_time = Helpers.get_start_time!() + + if DateTime.compare(start_time, DateTime.utc_now()) == :lt do + {:ok, socket + |> push_navigate(to: ~p"/app")} + else + {:ok, socket |> assign(:start_time, start_time)} + end + end +end diff --git a/lib/safira_web/plugs/event_roles.ex b/lib/safira_web/plugs/event_roles.ex index 0019b039..2a34713c 100644 --- a/lib/safira_web/plugs/event_roles.ex +++ b/lib/safira_web/plugs/event_roles.ex @@ -5,25 +5,29 @@ defmodule SafiraWeb.EventRoles do import Plug.Conn import Phoenix.Controller + alias Safira.Constants + alias SafiraWeb.Helpers + @doc """ Used to check if registrations have opened, so users can register / log in """ def registrations_open(conn, _opts) do - if Application.fetch_env!(:safira, SafiraWeb.Endpoint)[:registrations_open] do + {:ok, open} = Constants.get("REGISTRATIONS_OPEN") + if open do conn else raise %Ecto.NoResultsError{} end end + @doc """ + Used to check if attendees can already access the backoffice + """ def backoffice_enabled(conn, _opts) do attendees_allowed = - is_not_in_future(Application.fetch_env!(:safira, SafiraWeb.Endpoint)[:start_time]) - - staff_allowed = Application.fetch_env!(:safira, SafiraWeb.Endpoint)[:backoffice_enabled_staff] + is_not_in_future(Helpers.get_start_time!()) - if (conn.assigns.current_user.type == :attendee and attendees_allowed) or - (conn.assigns.current_user.type == :staff and staff_allowed) do + if (conn.assigns.current_user.type == :attendee and attendees_allowed) do conn else conn diff --git a/lib/safira_web/router.ex b/lib/safira_web/router.ex index ae5321a5..95c87090 100644 --- a/lib/safira_web/router.ex +++ b/lib/safira_web/router.ex @@ -23,7 +23,6 @@ defmodule SafiraWeb.Router do pipe_through :browser get "/", PageController, :home - get "/countdown", PageController, :countdown end # Other scopes may use custom stacks. @@ -51,14 +50,17 @@ defmodule SafiraWeb.Router do ## Authentication routes scope "/", SafiraWeb do - pipe_through [:browser, :redirect_if_user_is_authenticated, :registrations_open] + pipe_through [:browser, :redirect_if_user_is_authenticated] live_session :redirect_if_user_is_authenticated, on_mount: [{SafiraWeb.UserAuth, :redirect_if_user_is_authenticated}] do - live "/users/register", UserRegistrationLive, :new + live "/users/log_in", UserLoginLive, :new live "/users/reset_password", UserForgotPasswordLive, :new live "/users/reset_password/:token", UserResetPasswordLive, :edit + + pipe_through :registrations_open + live "/users/register", UserRegistrationLive, :new end post "/users/log_in", UserSessionController, :create @@ -74,6 +76,11 @@ defmodule SafiraWeb.Router do live "/scanner", ScannerLive.Index, :index + scope "/waiting", Waiting do + pipe_through [:require_attendee_user] + live "/countdown", CountdownLive, :countdown + end + scope "/app", App do pipe_through [:require_attendee_user, :backoffice_enabled] diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index a74aa58a..376fb6ed 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -9,6 +9,7 @@ defmodule Safira.Repo.Seeds do def run do [ "roles.exs", + "constants.exs", "courses.exs", "accounts.exs", "badges.exs", diff --git a/priv/repo/seeds/constants.exs b/priv/repo/seeds/constants.exs new file mode 100644 index 00000000..4137006a --- /dev/null +++ b/priv/repo/seeds/constants.exs @@ -0,0 +1,10 @@ +defmodule Safira.Repo.Seeds.Constants do + alias Safira.Constants + + def run do + Constants.set("REGISTRATIONS_OPEN", "true") + Constants.set("START_TIME", "2024-09-29T17:57:00Z") + end +end + +Safira.Repo.Seeds.Constants.run() From 0ef0a2ea1e093b3f7dd58f6bb2afa1e0570f6d96 Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Sun, 29 Sep 2024 17:03:14 +0100 Subject: [PATCH 03/16] feat: admin dashboard event page --- lib/safira/accounts/roles/permissions.ex | 3 +- lib/safira_web/config.ex | 7 ++ lib/safira_web/helpers.ex | 2 +- lib/safira_web/live/auth/user_login_live.ex | 14 ++-- .../backoffice/event_live/form_component.ex | 77 ++++++++++++++++++ .../live/backoffice/event_live/index.ex | 27 +++++++ .../backoffice/event_live/index.html.heex | 27 +++++++ lib/safira_web/live/waiting/countdown_live.ex | 80 +++++++++---------- lib/safira_web/plugs/event_roles.ex | 7 +- lib/safira_web/router.ex | 10 ++- 10 files changed, 199 insertions(+), 55 deletions(-) create mode 100644 lib/safira_web/live/backoffice/event_live/form_component.ex create mode 100644 lib/safira_web/live/backoffice/event_live/index.ex create mode 100644 lib/safira_web/live/backoffice/event_live/index.html.heex diff --git a/lib/safira/accounts/roles/permissions.ex b/lib/safira/accounts/roles/permissions.ex index 69da4280..70e09eca 100644 --- a/lib/safira/accounts/roles/permissions.ex +++ b/lib/safira/accounts/roles/permissions.ex @@ -16,7 +16,8 @@ defmodule Safira.Accounts.Roles.Permissions do "spotlights" => ["edit"], "schedule" => ["edit"], "statistics" => ["show"], - "mailer" => ["send"] + "mailer" => ["send"], + "event" => ["edit"] } end diff --git a/lib/safira_web/config.ex b/lib/safira_web/config.ex index 0acf081a..30d398c7 100644 --- a/lib/safira_web/config.ex +++ b/lib/safira_web/config.ex @@ -131,6 +131,13 @@ defmodule SafiraWeb.Config do icon: "hero-qr-code", url: "/dashboard/scanner", scope: %{"scanner" => ["show"]} + }, + %{ + key: :event, + title: "Event", + icon: "hero-cog-8-tooth", + url: "/dashboard/event", + scope: %{"event" => ["edit"]} } ] |> Enum.filter(fn page -> has_permission?(permissions, page.scope) end) diff --git a/lib/safira_web/helpers.ex b/lib/safira_web/helpers.ex index 19553fae..39a8aad2 100644 --- a/lib/safira_web/helpers.ex +++ b/lib/safira_web/helpers.ex @@ -164,7 +164,7 @@ defmodule SafiraWeb.Helpers do end def registrations_open? do - {:ok, registrations_open} = Constants.get("REGISTRATIONS_OPEN") + {:ok, registrations_open} = Constants.get("REGISTRATIONS_OPEN") string_to_bool(registrations_open) end diff --git a/lib/safira_web/live/auth/user_login_live.ex b/lib/safira_web/live/auth/user_login_live.ex index 5187a4cf..7efddf8b 100644 --- a/lib/safira_web/live/auth/user_login_live.ex +++ b/lib/safira_web/live/auth/user_login_live.ex @@ -14,12 +14,11 @@ defmodule SafiraWeb.UserLoginLive do Log in to account <:subtitle> <%= if @registrations_open do %> - - Don't have an account? - <.link navigate={~p"/users/register"} class="font-semibold text-primary hover:underline"> - Sign up - - for an account now. + Don't have an account? + <.link navigate={~p"/users/register"} class="font-semibold text-primary hover:underline"> + Sign up + + for an account now. <% end %> @@ -48,6 +47,7 @@ defmodule SafiraWeb.UserLoginLive do email = Phoenix.Flash.get(socket.assigns.flash, :email) form = to_form(%{"email" => email}, as: "user") - {:ok, assign(socket, form: form) |> assign(registrations_open: registrations_open?()), temporary_assigns: [form: form]} + {:ok, assign(socket, form: form) |> assign(registrations_open: registrations_open?()), + temporary_assigns: [form: form]} end end diff --git a/lib/safira_web/live/backoffice/event_live/form_component.ex b/lib/safira_web/live/backoffice/event_live/form_component.ex new file mode 100644 index 00000000..b6771c57 --- /dev/null +++ b/lib/safira_web/live/backoffice/event_live/form_component.ex @@ -0,0 +1,77 @@ +defmodule SafiraWeb.Backoffice.EventLive.FormComponent do + use SafiraWeb, :live_component + + import SafiraWeb.Components.Forms + + alias Safira.Constants + + @impl true + def render(assigns) do + ~H""" +
+ <.page + title={@title} + subtitle={gettext("Generic event settings. Careful with what you change.")} + > + <.simple_form + for={@form} + id="event-form" + phx-target={@myself} + phx-change="validate" + phx-submit="save" + > +
+
+ <.field field={@form[:registrations_open]} type="checkbox" label="Registrations Open" /> + <.field + field={@form[:start_time]} + type="datetime-local" + label="Start Date/Time" + required + /> +
+
+ <:actions> + <.button + data-confirm="Do you want to save these changes? It can break stuff if you are not careful" + phx-disable-with="Saving..." + > + Save Settings + + + + +
+ """ + end + + @impl true + def mount(socket) do + {:ok, socket} + end + + @impl true + def update(assigns, socket) do + {:ok, assign(socket, assigns)} + end + + @impl true + def handle_event("validate", _params, socket) do + {:noreply, socket} + end + + def handle_event("save", params, socket) do + Constants.set("REGISTRATIONS_OPEN", params["registrations_open"]) + Constants.set("START_TIME", DateTime.to_iso8601(parse_date(params["start_time"]))) + + {:noreply, + socket + |> put_flash(:info, "Event settings updated successfully") + |> push_patch(to: socket.assigns.patch)} + end + + defp parse_date(date_str) do + {:ok, date, _} = DateTime.from_iso8601("#{date_str}:00Z") + date + end +end diff --git a/lib/safira_web/live/backoffice/event_live/index.ex b/lib/safira_web/live/backoffice/event_live/index.ex new file mode 100644 index 00000000..e6141a77 --- /dev/null +++ b/lib/safira_web/live/backoffice/event_live/index.ex @@ -0,0 +1,27 @@ +defmodule SafiraWeb.Backoffice.EventLive.Index do + use SafiraWeb, :backoffice_view + + alias SafiraWeb.Helpers + + def mount(_params, _session, socket) do + registrations_open = Helpers.registrations_open?() + start_time = Helpers.get_start_time!() |> parse_date() + form = to_form(%{"registrations_open" => registrations_open, "start_time" => start_time}) + + {:ok, + socket + |> assign(:current_page, :event) + |> assign(form: form)} + end + + @impl true + def handle_params(_params, _url, socket) do + {:noreply, socket} + end + + defp parse_date(date) do + base_str = DateTime.to_iso8601(date) + len = String.length(base_str) + String.slice(base_str, 0, len - 4) + end +end diff --git a/lib/safira_web/live/backoffice/event_live/index.html.heex b/lib/safira_web/live/backoffice/event_live/index.html.heex new file mode 100644 index 00000000..44239e89 --- /dev/null +++ b/lib/safira_web/live/backoffice/event_live/index.html.heex @@ -0,0 +1,27 @@ +<.page title="Event Settings"> + <:actions> + <.link patch={~p"/dashboard/event/edit"}> + <.button>Edit Settings + + + +
+

Registrations open: <%= @form[:registrations_open].value %>

+

Start Date/Time: <%= @form[:start_time].value %>

+
+ +<.modal + :if={@live_action in [:edit]} + id="event-modal" + show + on_cancel={JS.patch(~p"/dashboard/event")} +> + <.live_component + module={SafiraWeb.Backoffice.EventLive.FormComponent} + id={:constants} + title="Event Settings" + action={@live_action} + form={@form} + patch={~p"/dashboard/event"} + /> + diff --git a/lib/safira_web/live/waiting/countdown_live.ex b/lib/safira_web/live/waiting/countdown_live.ex index 5259f84e..8a2e18d7 100644 --- a/lib/safira_web/live/waiting/countdown_live.ex +++ b/lib/safira_web/live/waiting/countdown_live.ex @@ -6,52 +6,51 @@ defmodule SafiraWeb.Waiting.CountdownLive do def render(assigns) do ~H"""
-

Countdown Timer

-
+

Countdown Timer

+
- -
+ const countdownInterval = setInterval(function() { + const now = new Date().getTime(); + let distance = Math.round((endTime - now) / 1000); + if (distance > 0) { + const distance_str = format_distance(distance) + countdownElement.innerHTML = "Time Remaining: " + distance_str; + } else { + countdownElement.innerHTML = "Time's up!"; + clearInterval(countdownInterval); + window.location.reload(); + } + }, 1000); + +
""" end @@ -59,8 +58,9 @@ defmodule SafiraWeb.Waiting.CountdownLive do start_time = Helpers.get_start_time!() if DateTime.compare(start_time, DateTime.utc_now()) == :lt do - {:ok, socket - |> push_navigate(to: ~p"/app")} + {:ok, + socket + |> push_navigate(to: ~p"/app")} else {:ok, socket |> assign(:start_time, start_time)} end diff --git a/lib/safira_web/plugs/event_roles.ex b/lib/safira_web/plugs/event_roles.ex index 2a34713c..9d171ca3 100644 --- a/lib/safira_web/plugs/event_roles.ex +++ b/lib/safira_web/plugs/event_roles.ex @@ -13,6 +13,7 @@ defmodule SafiraWeb.EventRoles do """ def registrations_open(conn, _opts) do {:ok, open} = Constants.get("REGISTRATIONS_OPEN") + if open do conn else @@ -27,12 +28,12 @@ defmodule SafiraWeb.EventRoles do attendees_allowed = is_not_in_future(Helpers.get_start_time!()) - if (conn.assigns.current_user.type == :attendee and attendees_allowed) do + if conn.assigns.current_user.type == :attendee and not attendees_allowed do conn + |> redirect(to: ~p"/waiting/countdown") + |> halt() else conn - |> redirect(to: ~p"/countdown") - |> halt() end end diff --git a/lib/safira_web/router.ex b/lib/safira_web/router.ex index 95c87090..9e41ceec 100644 --- a/lib/safira_web/router.ex +++ b/lib/safira_web/router.ex @@ -54,13 +54,12 @@ defmodule SafiraWeb.Router do live_session :redirect_if_user_is_authenticated, on_mount: [{SafiraWeb.UserAuth, :redirect_if_user_is_authenticated}] do - live "/users/log_in", UserLoginLive, :new live "/users/reset_password", UserForgotPasswordLive, :new live "/users/reset_password/:token", UserResetPasswordLive, :edit pipe_through :registrations_open - live "/users/register", UserRegistrationLive, :new + live "/users/register", UserRegistrationLive, :new end post "/users/log_in", UserSessionController, :create @@ -105,13 +104,18 @@ defmodule SafiraWeb.Router do end scope "/dashboard", Backoffice do - pipe_through [:require_staff_user, :backoffice_enabled] + pipe_through [:require_staff_user] scope "/attendees", AttendeeLive do live "/", Index, :index live "/:id", Show, :show end + scope "/event", EventLive do + live "/", Index, :index + live "/edit", Index, :edit + end + scope "/staffs", StaffLive do live "/", Index, :index live "/:id/edit", Index, :edit From 9c11b07f6051fcf1a056994cef83b9b6cac61329 Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Sun, 29 Sep 2024 17:28:56 +0100 Subject: [PATCH 04/16] chore: remove unused --- .env.dev.sample | 5 +---- config/runtime.exs | 8 -------- mix.exs | 1 - mix.lock | 47 +++++++++++++++++++++++----------------------- 4 files changed, 24 insertions(+), 37 deletions(-) diff --git a/.env.dev.sample b/.env.dev.sample index 7d9fd60c..bf9d901a 100644 --- a/.env.dev.sample +++ b/.env.dev.sample @@ -4,7 +4,4 @@ DB_HOST=localhost DB_PORT=5432 DB_NAME=safira_dev HOST_URL=http://localhost:4000 -ASSET_HOST=http://localhost:4000 -REGISTRATIONS_OPEN=true -BACKOFFICE_ENABLED_STAFF=true -START_TIME=2024-02-06T09:00:00Z \ No newline at end of file +ASSET_HOST=http://localhost:4000 \ No newline at end of file diff --git a/config/runtime.exs b/config/runtime.exs index 3a170a69..a74bdb45 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -1,5 +1,4 @@ import Config -import Dotenvy # config/runtime.exs is executed for all environments, including # during releases. It is executed after compilation and before the @@ -21,13 +20,6 @@ if System.get_env("PHX_SERVER") do config :safira, SafiraWeb.Endpoint, server: true end -source([".env.dev", System.get_env()]) - -config :safira, SafiraWeb.Endpoint, - registrations_open: env!("REGISTRATIONS_OPEN", :boolean, true), - backoffice_enabled_staff: env!("BACKOFFICE_ENABLED_STAFF", :boolean, true), - start_time: DateTime.from_iso8601(env!("START_TIME", :string!)) |> elem(1) - if config_env() == :prod do database_url = System.get_env("DATABASE_URL") || diff --git a/mix.exs b/mix.exs index 04c7e1ff..4a5c8a67 100644 --- a/mix.exs +++ b/mix.exs @@ -83,7 +83,6 @@ defmodule Safira.MixProject do {:jason, "~> 1.2"}, {:lua, "~> 0.0.14"}, {:timex, "~> 3.7.11"}, - {:dotenvy, "~> 0.8.0"}, # server {:bandit, "~> 1.2"}, diff --git a/mix.lock b/mix.lock index da1462e8..d9dafaa8 100644 --- a/mix.lock +++ b/mix.lock @@ -1,38 +1,37 @@ %{ - "bandit": {:hex, :bandit, "1.5.5", "df28f1c41f745401fe9e85a6882033f5f3442ab6d30c8a2948554062a4ab56e0", [:mix], [{:hpax, "~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f21579a29ea4bc08440343b2b5f16f7cddf2fea5725d31b72cf973ec729079e1"}, - "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.1.0", "0b110a9a6c619b19a7f73fa3004aa11d6e719a67e672d1633dc36b6b2290a0f7", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2ad2acb5a8bc049e8d5aa267802631912bb80d5f4110a178ae7999e69dca1bf7"}, + "bandit": {:hex, :bandit, "1.5.7", "6856b1e1df4f2b0cb3df1377eab7891bec2da6a7fd69dc78594ad3e152363a50", [:mix], [{:hpax, "~> 1.0.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f2dd92ae87d2cbea2fa9aa1652db157b6cba6c405cb44d4f6dd87abba41371cd"}, + "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.2.0", "feab711974beba4cb348147170346fe097eea2e840db4e012a145e180ed4ab75", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "563e92a6c77d667b19c5f4ba17ab6d440a085696bdf4c68b9b0f5b30bc5422b8"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"}, - "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, + "castore": {:hex, :castore, "1.0.9", "5cc77474afadf02c7c017823f460a17daa7908e991b0cc917febc90e466a375c", [:mix], [], "hexpm", "5ea956504f1ba6f2b4eb707061d8e17870de2bee95fb59d512872c2ef06925e7"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, - "comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"}, - "credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"}, + "comeonin": {:hex, :comeonin, "5.5.0", "364d00df52545c44a139bad919d7eacb55abf39e86565878e17cebb787977368", [:mix], [], "hexpm", "6287fc3ba0aad34883cbe3f7949fc1d1e738e5ccdce77165bc99490aa69f47fb"}, + "credo": {:hex, :credo, "1.7.10", "6e64fe59be8da5e30a1b96273b247b5cf1cc9e336b5fd66302a64b25749ad44d", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "71fbc9a6b8be21d993deca85bf151df023a3097b01e09a2809d460348561d8cd"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"}, - "dotenvy": {:hex, :dotenvy, "0.8.0", "777486ad485668317c56afc53a7cbcd74f43e4e34588ba8e95a73e15a360050e", [:mix], [], "hexpm", "1f535066282388cbd109743d337ac46ff0708195780d4b5778bb83491ab1b654"}, - "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, - "ecto_sql": {:hex, :ecto_sql, "3.11.3", "4eb7348ff8101fbc4e6bbc5a4404a24fecbe73a3372d16569526b0cf34ebc195", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e5f36e3d736b99c7fee3e631333b8394ade4bafe9d96d35669fca2d81c2be928"}, + "ecto": {:hex, :ecto, "3.12.4", "267c94d9f2969e6acc4dd5e3e3af5b05cdae89a4d549925f3008b2b7eb0b93c3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef04e4101688a67d061e1b10d7bc1fbf00d1d13c17eef08b71d070ff9188f747"}, + "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, "elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"}, - "esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"}, + "esbuild": {:hex, :esbuild, "0.8.2", "5f379dfa383ef482b738e7771daf238b2d1cfb0222bef9d3b20d4c8f06c7a7ac", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "558a8a08ed78eb820efbfda1de196569d8bfa9b51e8371a1934fbb31345feda7"}, "eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"}, - "expo": {:hex, :expo, "0.5.2", "beba786aab8e3c5431813d7a44b828e7b922bfa431d6bfbada0904535342efe2", [:mix], [], "hexpm", "8c9bfa06ca017c9cb4020fabe980bc7fdb1aaec059fd004c2ab3bff03b1c599c"}, + "expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"}, "faker": {:hex, :faker, "0.18.0", "943e479319a22ea4e8e39e8e076b81c02827d9302f3d32726c5bf82f430e6e14", [:mix], [], "hexpm", "bfbdd83958d78e2788e99ec9317c4816e651ad05e24cfd1196ce5db5b3e81797"}, - "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, - "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, - "floki": {:hex, :floki, "0.36.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"}, + "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, + "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, + "floki": {:hex, :floki, "0.36.3", "1102f93b16a55bc5383b85ae3ec470f82dee056eaeff9195e8afdf0ef2a43c30", [:mix], [], "hexpm", "fe0158bff509e407735f6d40b3ee0d7deb47f3f3ee7c6c182ad28599f9f6b27a"}, "flop": {:hex, :flop, "0.25.0", "0667f3c65f140b2ed7d64dad01b8e0e3dcc071addf8a556d11b36cad1619a6c1", [:mix], [{:ecto, "~> 3.11", [hex: :ecto, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}], "hexpm", "7e4b01b76412c77691e9aaea6903c5a8212fb7243198e1d2ba74fa15f2aec34c"}, - "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, + "gettext": {:hex, :gettext, "0.26.1", "38e14ea5dcf962d1fc9f361b63ea07c0ce715a8ef1f9e82d3dfb8e67e0416715", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "01ce56f188b9dc28780a52783d6529ad2bc7124f9744e571e1ee4ea88bf08734"}, "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized", depth: 1]}, - "hpax": {:hex, :hpax, "0.2.0", "5a58219adcb75977b2edce5eb22051de9362f08236220c9e859a47111c194ff5", [:mix], [], "hexpm", "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"}, + "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, - "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"}, "live_select": {:hex, :live_select, "1.4.3", "ec9706952f589d8e2e6f98a0e1633c5b51ab5b807d503bd0d9622a26c999fb9a", [:mix], [{:ecto, "~> 3.8", [hex: :ecto, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.6.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_html_helpers, "~> 1.0", [hex: :phoenix_html_helpers, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "58f7d702b0f786c73d31e60a342c0a49afaf56ca5a6a078b51babf3490465220"}, - "lua": {:hex, :lua, "0.0.14", "0f9f2b44271debdf855efe87583f73e874c4daec1e920c45a73d1fa8e3c2f9a8", [:mix], [{:luerl, "~> 1.2", [hex: :luerl, repo: "hexpm", optional: false]}], "hexpm", "9bd39736c349dd47541a5619925f00d7cf3f2a6d3d33248b80f9eac81f5850d3"}, - "luerl": {:hex, :luerl, "1.2.0", "60f05f4240f0e7c148ddb79b67b8ff972734aad237aa74c83d0748b8214c8ef0", [:rebar3], [], "hexpm", "9cafd4f6094ff0f5a9d278fd81d60d3e026c820bdfb6cacd4b1bd909f21b525d"}, + "lua": {:hex, :lua, "0.0.21", "70130ad3ca895858b4aa440bc1c37a76e2122da1ef7f20c9b69c33077305c96b", [:mix], [{:luerl, "~> 1.2", [hex: :luerl, repo: "hexpm", optional: false]}], "hexpm", "8562da85d043a39e6aaaaf131a4b9f2c00bb565976a027481df5e7ba21962097"}, + "luerl": {:hex, :luerl, "1.2.3", "df25f41944e57a7c4d9ef09d238bc3e850276c46039cfc12b8bb42eccf36fcb1", [:rebar3], [], "hexpm", "1b4b9d0ca5d7d280d1d2787a6a5ee9f5a212641b62bff91556baa53805df3aed"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, @@ -41,7 +40,7 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"}, - "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.2", "3b83b24ab5a2eb071a20372f740d7118767c272db386831b2e77638c4dcc606d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "3f94d025f59de86be00f5f8c5dd7b5965a3298458d21ab1c328488be3b5fcd59"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.3", "f686701b0499a07f2e3b122d84d52ff8a31f5def386e03706c916f6feddf69ef", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "909502956916a657a197f94cc1206d9a65247538de8a5e186f7537c895d95764"}, "phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"}, "phoenix_html_helpers": {:hex, :phoenix_html_helpers, "1.0.1", "7eed85c52eff80a179391036931791ee5d2f713d76a81d0d2c6ebafe1e11e5ec", [:mix], [{:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cffd2385d1fa4f78b04432df69ab8da63dc5cf63e07b713a4dcf36a3740e3090"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.4", "4508e481f791ce62ec6a096e13b061387158cbeefacca68c6c1928e1305e23ed", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "2984aae96994fbc5c61795a73b8fb58153b41ff934019cfb522343d2d3817d59"}, @@ -51,13 +50,13 @@ "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, - "postgrex": {:hex, :postgrex, "0.18.0", "f34664101eaca11ff24481ed4c378492fed2ff416cd9b06c399e90f321867d7e", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a042989ba1bc1cca7383ebb9e461398e3f89f868c92ce6671feb7ef132a252d1"}, + "postgrex": {:hex, :postgrex, "0.19.2", "34d6884a332c7bf1e367fc8b9a849d23b43f7da5c6e263def92784d03f9da468", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "618988886ab7ae8561ebed9a3c7469034bf6a88b8995785a3378746a4b9835ec"}, "qrcode_ex": {:hex, :qrcode_ex, "0.1.1", "8907a7558325babd30f7f43ff85a0169ef65c30820d68e90d792802318f9a062", [:mix], [], "hexpm", "9eb0b397fb3a1c3b16e55b6de6f845a0b4e7b7100ade39eb59fad98fb62455a7"}, "sleeplocks": {:hex, :sleeplocks, "1.1.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "swoosh": {:hex, :swoosh, "1.16.9", "20c6a32ea49136a4c19f538e27739bb5070558c0fa76b8a95f4d5d5ca7d319a1", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.0", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "878b1a7a6c10ebbf725a3349363f48f79c5e3d792eb621643b0d276a38acc0a6"}, - "tailwind": {:hex, :tailwind, "0.2.3", "277f08145d407de49650d0a4685dc062174bdd1ae7731c5f1da86163a24dfcdb", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "8e45e7a34a676a7747d04f7913a96c770c85e6be810a1d7f91e713d3a3655b5d"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "swoosh": {:hex, :swoosh, "1.17.3", "5cda7bff6bc1121cc5b58db8ed90ef33261b373425ae3e32dd599688037a0482", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "14ad57cfbb70af57323e17f569f5840a33c01f8ebc531dd3846beef3c9c95e55"}, + "tailwind": {:hex, :tailwind, "0.2.4", "5706ec47182d4e7045901302bf3a333e80f3d1af65c442ba9a9eed152fb26c2e", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "c6e4a82b8727bab593700c998a4d98cf3d8025678bfde059aed71d0000c3e463"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "telemetry_metrics": {:hex, :telemetry_metrics, "1.0.0", "29f5f84991ca98b8eb02fc208b2e6de7c95f8bb2294ef244a176675adc7775df", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f23713b3847286a534e005126d4c959ebcca68ae9582118ce436b521d1d47d5d"}, "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, "thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"}, @@ -68,5 +67,5 @@ "waffle": {:hex, :waffle, "1.1.9", "8ce5ca9e59fa5491da67a2df57b8711d93223df3c3e5c21ad2acdedc41a0f51a", [:mix], [{:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:ex_aws_s3, "~> 2.1", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "307c63cfdfb4624e7c423868a128ccfcb0e5291ae73a9deecb3a10b7a3eb277c"}, "waffle_ecto": {:hex, :waffle_ecto, "0.0.12", "e5c17c49b071b903df71861c642093281123142dc4e9908c930db3e06795b040", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:waffle, "~> 1.0", [hex: :waffle, repo: "hexpm", optional: false]}], "hexpm", "585fe6371057066d2e8e3383ddd7a2437ff0668caf3f4cbf5a041e0de9837168"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, - "websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.7", "65fa74042530064ef0570b75b43f5c49bb8b235d6515671b3d250022cb8a1f9e", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "d0f478ee64deddfec64b800673fd6e0c8888b079d9f3444dd96d2a98383bdbd1"}, } From df40fa712e4d9ff8de31cd4bb4dca31ca86dd1d1 Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Wed, 2 Oct 2024 12:14:55 +0100 Subject: [PATCH 05/16] refactor: move to hook --- assets/js/app.js | 3 +- assets/js/hooks/countdown.js | 52 ++++++++++++++ assets/js/hooks/index.js | 3 +- lib/safira_web/helpers.ex | 4 +- lib/safira_web/live/app/waiting_live/index.ex | 28 ++++++++ .../backoffice/event_live/form_component.ex | 6 +- .../backoffice/event_live/index.html.heex | 6 +- lib/safira_web/live/waiting/countdown_live.ex | 68 ------------------- lib/safira_web/plugs/event_roles.ex | 9 +-- lib/safira_web/router.ex | 10 +-- 10 files changed, 102 insertions(+), 87 deletions(-) create mode 100644 assets/js/hooks/countdown.js create mode 100644 lib/safira_web/live/app/waiting_live/index.ex delete mode 100644 lib/safira_web/live/waiting/countdown_live.ex diff --git a/assets/js/app.js b/assets/js/app.js index 7ebc74dc..dc5601ed 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -22,12 +22,13 @@ import {Socket} from "phoenix" import {LiveSocket} from "phoenix_live_view" import topbar from "../vendor/topbar" import live_select from "live_select" -import { QrScanner, Wheel, Confetti, Sorting } from "./hooks"; +import { QrScanner, Wheel, Confetti, Countdown, Sorting } from "./hooks"; let Hooks = { QrScanner: QrScanner, Wheel: Wheel, Confetti: Confetti, + Countdown: Countdown, Sorting: Sorting, ...live_select }; diff --git a/assets/js/hooks/countdown.js b/assets/js/hooks/countdown.js new file mode 100644 index 00000000..d56eca0e --- /dev/null +++ b/assets/js/hooks/countdown.js @@ -0,0 +1,52 @@ +export const Countdown = { + mounted() { + const timeReceived = (start_time) => { + if(this.clock !== undefined) { + clearInterval(this.clock); + } + + this.clock = setInterval(() => { + const now = new Date().getTime(); + const seconds_left = Math.round((start_time - now) / 1000); + const text_element = document.getElementById("seconds-remaining"); + + if(seconds_left >= 0) { + text_element.innerHTML = formatTimeRemaining(seconds_left); + } else { + text_element.innerText = "00"; + clearInterval(this.clock); + window.location.reload(); + } + }, 1000); + }; + + window.addEventListener("phx:highlight", (e) => + timeReceived(new Date(e.detail.start_time).getTime())); + } +} + +function formatTimeRemaining(seconds) { + // Calculate days, hours, minutes, and seconds + const days = Math.floor(seconds / (24 * 60 * 60)); // Calculate total days + seconds %= 24 * 60 * 60; // Get remaining seconds after extracting days + const hours = Math.floor(seconds / 3600); // Calculate hours + seconds %= 3600; // Get remaining seconds after extracting hours + const minutes = Math.floor(seconds / 60); // Calculate minutes + const remainingSeconds = seconds % 60; // Remaining seconds + + // Format hours, minutes, and seconds to always be two digits + const formattedHours = String(hours).padStart(2, '0'); + const formattedMinutes = String(minutes).padStart(2, '0'); + const formattedSeconds = String(remainingSeconds).padStart(2, '0'); + + if(days > 0) + return `${days} days, ${formattedHours}:${formattedMinutes}:${formattedSeconds}`; + + if(hours > 0) + return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`; + + if(minutes > 0) + return `${formattedMinutes}:${formattedSeconds}`; + + return `${seconds}`; +} \ No newline at end of file diff --git a/assets/js/hooks/index.js b/assets/js/hooks/index.js index b871780b..3f052bb2 100644 --- a/assets/js/hooks/index.js +++ b/assets/js/hooks/index.js @@ -1,4 +1,5 @@ export { QrScanner } from "./qr_reading.js"; export { Wheel } from "./wheel.js"; export { Confetti } from "./confetti.js"; -export { Sorting } from "./sorting.js"; \ No newline at end of file +export { Sorting } from "./sorting.js"; +export { Countdown } from "./countdown.js"; diff --git a/lib/safira_web/helpers.ex b/lib/safira_web/helpers.ex index 39a8aad2..42ca9d4d 100644 --- a/lib/safira_web/helpers.ex +++ b/lib/safira_web/helpers.ex @@ -164,12 +164,12 @@ defmodule SafiraWeb.Helpers do end def registrations_open? do - {:ok, registrations_open} = Constants.get("REGISTRATIONS_OPEN") + {:ok, registrations_open} = Constants.get("registrations_open") string_to_bool(registrations_open) end def get_start_time! do - {:ok, start_str} = Constants.get("START_TIME") + {:ok, start_str} = Constants.get("start_time") {:ok, start_time, _} = DateTime.from_iso8601(start_str) start_time end diff --git a/lib/safira_web/live/app/waiting_live/index.ex b/lib/safira_web/live/app/waiting_live/index.ex new file mode 100644 index 00000000..29ea9cc1 --- /dev/null +++ b/lib/safira_web/live/app/waiting_live/index.ex @@ -0,0 +1,28 @@ +defmodule SafiraWeb.App.WaitingLive.Index do + use SafiraWeb, :live_view + + alias SafiraWeb.Helpers + + def render(assigns) do + ~H""" +
+

Countdown Timer

+
+
+ """ + end + + def mount(_params, _session, socket) do + start_time = Helpers.get_start_time!() + + if DateTime.compare(start_time, DateTime.utc_now()) == :lt do + {:ok, + socket + |> push_navigate(to: ~p"/app")} + else + {:ok, + socket + |> push_event("highlight", %{start_time: start_time})} + end + end +end diff --git a/lib/safira_web/live/backoffice/event_live/form_component.ex b/lib/safira_web/live/backoffice/event_live/form_component.ex index b6771c57..b50f9d66 100644 --- a/lib/safira_web/live/backoffice/event_live/form_component.ex +++ b/lib/safira_web/live/backoffice/event_live/form_component.ex @@ -36,7 +36,7 @@ defmodule SafiraWeb.Backoffice.EventLive.FormComponent do data-confirm="Do you want to save these changes? It can break stuff if you are not careful" phx-disable-with="Saving..." > - Save Settings + <%= gettext("Save Settings") %> @@ -61,8 +61,8 @@ defmodule SafiraWeb.Backoffice.EventLive.FormComponent do end def handle_event("save", params, socket) do - Constants.set("REGISTRATIONS_OPEN", params["registrations_open"]) - Constants.set("START_TIME", DateTime.to_iso8601(parse_date(params["start_time"]))) + Constants.set("registrations_open", params["registrations_open"]) + Constants.set("start_time", DateTime.to_iso8601(parse_date(params["start_time"]))) {:noreply, socket diff --git a/lib/safira_web/live/backoffice/event_live/index.html.heex b/lib/safira_web/live/backoffice/event_live/index.html.heex index 44239e89..cabba574 100644 --- a/lib/safira_web/live/backoffice/event_live/index.html.heex +++ b/lib/safira_web/live/backoffice/event_live/index.html.heex @@ -1,13 +1,13 @@ <.page title="Event Settings"> <:actions> <.link patch={~p"/dashboard/event/edit"}> - <.button>Edit Settings + <.button><%= gettext("Edit Settings") %>
-

Registrations open: <%= @form[:registrations_open].value %>

-

Start Date/Time: <%= @form[:start_time].value %>

+

<%= gettext("Registrations open:") %> <%= @form[:registrations_open].value %>

+

<%= gettext("Start Date/Time:") %> <%= @form[:start_time].value %>

<.modal diff --git a/lib/safira_web/live/waiting/countdown_live.ex b/lib/safira_web/live/waiting/countdown_live.ex deleted file mode 100644 index 8a2e18d7..00000000 --- a/lib/safira_web/live/waiting/countdown_live.ex +++ /dev/null @@ -1,68 +0,0 @@ -defmodule SafiraWeb.Waiting.CountdownLive do - use SafiraWeb, :live_view - - alias SafiraWeb.Helpers - - def render(assigns) do - ~H""" -
-

Countdown Timer

-
- - -
- """ - end - - def mount(_params, _session, socket) do - start_time = Helpers.get_start_time!() - - if DateTime.compare(start_time, DateTime.utc_now()) == :lt do - {:ok, - socket - |> push_navigate(to: ~p"/app")} - else - {:ok, socket |> assign(:start_time, start_time)} - end - end -end diff --git a/lib/safira_web/plugs/event_roles.ex b/lib/safira_web/plugs/event_roles.ex index 9d171ca3..3b8ad62c 100644 --- a/lib/safira_web/plugs/event_roles.ex +++ b/lib/safira_web/plugs/event_roles.ex @@ -1,4 +1,5 @@ defmodule SafiraWeb.EventRoles do + @moduledoc false alias Phoenix.Router.NoRouteError use SafiraWeb, :verified_routes @@ -12,7 +13,7 @@ defmodule SafiraWeb.EventRoles do Used to check if registrations have opened, so users can register / log in """ def registrations_open(conn, _opts) do - {:ok, open} = Constants.get("REGISTRATIONS_OPEN") + {:ok, open} = Constants.get("registrations_open") if open do conn @@ -26,18 +27,18 @@ defmodule SafiraWeb.EventRoles do """ def backoffice_enabled(conn, _opts) do attendees_allowed = - is_not_in_future(Helpers.get_start_time!()) + not_in_future?(Helpers.get_start_time!()) if conn.assigns.current_user.type == :attendee and not attendees_allowed do conn - |> redirect(to: ~p"/waiting/countdown") + |> redirect(to: ~p"/app/waiting") |> halt() else conn end end - defp is_not_in_future(time) do + defp not_in_future?(time) do DateTime.compare(time, DateTime.utc_now()) == :lt end end diff --git a/lib/safira_web/router.ex b/lib/safira_web/router.ex index 9e41ceec..4210936b 100644 --- a/lib/safira_web/router.ex +++ b/lib/safira_web/router.ex @@ -75,11 +75,6 @@ defmodule SafiraWeb.Router do live "/scanner", ScannerLive.Index, :index - scope "/waiting", Waiting do - pipe_through [:require_attendee_user] - live "/countdown", CountdownLive, :countdown - end - scope "/app", App do pipe_through [:require_attendee_user, :backoffice_enabled] @@ -89,6 +84,11 @@ defmodule SafiraWeb.Router do end pipe_through [:require_credential] + + live "/waiting", WaitingLive.Index, :index + + pipe_through [:require_credential] + live "/", HomeLive.Index, :index live "/credential", CredentialLive.Index, :index From 4bc1d500616224d3018aa1859b3bae58b89a09a3 Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Wed, 2 Oct 2024 12:25:30 +0100 Subject: [PATCH 06/16] chore: fix CI --- lib/safira_web/live/app/waiting_live/index.ex | 2 ++ lib/safira_web/live/auth/user_login_live.ex | 2 -- lib/safira_web/live/backoffice/event_live/index.ex | 1 + lib/safira_web/plugs/event_roles.ex | 2 +- priv/repo/seeds/constants.exs | 4 ++-- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/safira_web/live/app/waiting_live/index.ex b/lib/safira_web/live/app/waiting_live/index.ex index 29ea9cc1..e406c5c2 100644 --- a/lib/safira_web/live/app/waiting_live/index.ex +++ b/lib/safira_web/live/app/waiting_live/index.ex @@ -3,6 +3,7 @@ defmodule SafiraWeb.App.WaitingLive.Index do alias SafiraWeb.Helpers + @impl true def render(assigns) do ~H"""
@@ -12,6 +13,7 @@ defmodule SafiraWeb.App.WaitingLive.Index do """ end + @impl true def mount(_params, _session, socket) do start_time = Helpers.get_start_time!() diff --git a/lib/safira_web/live/auth/user_login_live.ex b/lib/safira_web/live/auth/user_login_live.ex index 7efddf8b..92400086 100644 --- a/lib/safira_web/live/auth/user_login_live.ex +++ b/lib/safira_web/live/auth/user_login_live.ex @@ -1,8 +1,6 @@ defmodule SafiraWeb.UserLoginLive do use SafiraWeb, :live_view - alias Safira.Constants - def render(assigns) do ~H"""
diff --git a/lib/safira_web/live/backoffice/event_live/index.ex b/lib/safira_web/live/backoffice/event_live/index.ex index e6141a77..142ecf7a 100644 --- a/lib/safira_web/live/backoffice/event_live/index.ex +++ b/lib/safira_web/live/backoffice/event_live/index.ex @@ -3,6 +3,7 @@ defmodule SafiraWeb.Backoffice.EventLive.Index do alias SafiraWeb.Helpers + @impl true def mount(_params, _session, socket) do registrations_open = Helpers.registrations_open?() start_time = Helpers.get_start_time!() |> parse_date() diff --git a/lib/safira_web/plugs/event_roles.ex b/lib/safira_web/plugs/event_roles.ex index 3b8ad62c..41e6a559 100644 --- a/lib/safira_web/plugs/event_roles.ex +++ b/lib/safira_web/plugs/event_roles.ex @@ -1,12 +1,12 @@ defmodule SafiraWeb.EventRoles do @moduledoc false - alias Phoenix.Router.NoRouteError use SafiraWeb, :verified_routes import Plug.Conn import Phoenix.Controller alias Safira.Constants + alias SafiraWeb.Helpers @doc """ diff --git a/priv/repo/seeds/constants.exs b/priv/repo/seeds/constants.exs index 4137006a..71530076 100644 --- a/priv/repo/seeds/constants.exs +++ b/priv/repo/seeds/constants.exs @@ -2,8 +2,8 @@ defmodule Safira.Repo.Seeds.Constants do alias Safira.Constants def run do - Constants.set("REGISTRATIONS_OPEN", "true") - Constants.set("START_TIME", "2024-09-29T17:57:00Z") + Constants.set("registrations_open", "true") + Constants.set("start_time", "2024-09-29T17:57:00Z") end end From bfb143d740d2a1cdf6f8aa0218c9dcb02aed42d6 Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Mon, 4 Nov 2024 20:30:09 +0000 Subject: [PATCH 07/16] chore: fix CI --- assets/js/hooks/countdown.js | 47 ++++++++++--------- lib/safira_web.ex | 4 +- lib/safira_web/components/core_components.ex | 2 +- lib/safira_web/components/table.ex | 2 +- lib/safira_web/components/table_search.ex | 2 +- .../controllers/error_html/404.html.heex | 2 +- .../controllers/error_html/500.html.heex | 1 + lib/safira_web/gettext.ex | 4 +- lib/safira_web/helpers.ex | 2 +- mix.exs | 2 +- .../controllers/error_html_test.exs | 4 +- .../user_session_controller_test.exs | 3 ++ 12 files changed, 40 insertions(+), 35 deletions(-) create mode 100644 lib/safira_web/controllers/error_html/500.html.heex diff --git a/assets/js/hooks/countdown.js b/assets/js/hooks/countdown.js index d56eca0e..515af443 100644 --- a/assets/js/hooks/countdown.js +++ b/assets/js/hooks/countdown.js @@ -26,27 +26,28 @@ export const Countdown = { } function formatTimeRemaining(seconds) { - // Calculate days, hours, minutes, and seconds - const days = Math.floor(seconds / (24 * 60 * 60)); // Calculate total days - seconds %= 24 * 60 * 60; // Get remaining seconds after extracting days - const hours = Math.floor(seconds / 3600); // Calculate hours - seconds %= 3600; // Get remaining seconds after extracting hours - const minutes = Math.floor(seconds / 60); // Calculate minutes - const remainingSeconds = seconds % 60; // Remaining seconds - - // Format hours, minutes, and seconds to always be two digits - const formattedHours = String(hours).padStart(2, '0'); - const formattedMinutes = String(minutes).padStart(2, '0'); - const formattedSeconds = String(remainingSeconds).padStart(2, '0'); - - if(days > 0) - return `${days} days, ${formattedHours}:${formattedMinutes}:${formattedSeconds}`; - - if(hours > 0) - return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`; - - if(minutes > 0) - return `${formattedMinutes}:${formattedSeconds}`; - - return `${seconds}`; + const timeUnits = { + days: Math.floor(seconds / (24 * 60 * 60)), + hours: Math.floor((seconds % (24 * 60 * 60)) / 3600), + minutes: Math.floor((seconds % 3600) / 60), + seconds: seconds % 60 + }; + + // Format units to two digits except for days + const formattedTime = { + hours: String(timeUnits.hours).padStart(2, '0'), + minutes: String(timeUnits.minutes).padStart(2, '0'), + seconds: String(timeUnits.seconds).padStart(2, '0') + }; + + if (timeUnits.days > 0) { + return `${timeUnits.days} days, ${formattedTime.hours}:${formattedTime.minutes}:${formattedTime.seconds}`; + } + if (timeUnits.hours > 0) { + return `${formattedTime.hours}:${formattedTime.minutes}:${formattedTime.seconds}`; + } + if (timeUnits.minutes > 0) { + return `${formattedTime.minutes}:${formattedTime.seconds}`; + } + return `${timeUnits.seconds}`; } \ No newline at end of file diff --git a/lib/safira_web.ex b/lib/safira_web.ex index cf3d9822..8dc7bec1 100644 --- a/lib/safira_web.ex +++ b/lib/safira_web.ex @@ -43,7 +43,7 @@ defmodule SafiraWeb do layouts: [html: SafiraWeb.Layouts] import Plug.Conn - import SafiraWeb.Gettext + use Gettext, backend: SafiraWeb.Gettext unquote(verified_routes()) end @@ -117,7 +117,7 @@ defmodule SafiraWeb do # Core UI components and translation import SafiraWeb.CoreComponents import SafiraWeb.Components.Page - import SafiraWeb.Gettext + use Gettext, backend: SafiraWeb.Gettext import SafiraWeb.Helpers diff --git a/lib/safira_web/components/core_components.ex b/lib/safira_web/components/core_components.ex index 061ebd5d..804d1318 100644 --- a/lib/safira_web/components/core_components.ex +++ b/lib/safira_web/components/core_components.ex @@ -19,7 +19,7 @@ defmodule SafiraWeb.CoreComponents do alias Phoenix.HTML.Form alias Phoenix.LiveView.JS - import SafiraWeb.Gettext + use Gettext, backend: SafiraWeb.Gettext @doc """ Renders a modal. diff --git a/lib/safira_web/components/table.ex b/lib/safira_web/components/table.ex index 978e9323..557c8ee7 100644 --- a/lib/safira_web/components/table.ex +++ b/lib/safira_web/components/table.ex @@ -5,7 +5,7 @@ defmodule SafiraWeb.Components.Table do use Phoenix.Component alias Plug.Conn.Query - import SafiraWeb.Gettext + use Gettext, backend: SafiraWeb.Gettexttext import SafiraWeb.CoreComponents attr :id, :string, required: true diff --git a/lib/safira_web/components/table_search.ex b/lib/safira_web/components/table_search.ex index 5da9c5b9..cddb2697 100644 --- a/lib/safira_web/components/table_search.ex +++ b/lib/safira_web/components/table_search.ex @@ -3,7 +3,7 @@ defmodule SafiraWeb.Components.TableSearch do Reusable table search component. """ use Phoenix.Component - import SafiraWeb.Gettext + use Gettext, backend: SafiraWeb.Gettext attr :id, :string, required: true attr :params, :map, required: true diff --git a/lib/safira_web/controllers/error_html/404.html.heex b/lib/safira_web/controllers/error_html/404.html.heex index 03332b4f..e3bf3146 100644 --- a/lib/safira_web/controllers/error_html/404.html.heex +++ b/lib/safira_web/controllers/error_html/404.html.heex @@ -1 +1 @@ -

Not Found

+Not Found diff --git a/lib/safira_web/controllers/error_html/500.html.heex b/lib/safira_web/controllers/error_html/500.html.heex new file mode 100644 index 00000000..2b452eba --- /dev/null +++ b/lib/safira_web/controllers/error_html/500.html.heex @@ -0,0 +1 @@ +Internal Server Error diff --git a/lib/safira_web/gettext.ex b/lib/safira_web/gettext.ex index 70031be0..c15caef8 100644 --- a/lib/safira_web/gettext.ex +++ b/lib/safira_web/gettext.ex @@ -5,7 +5,7 @@ defmodule SafiraWeb.Gettext do By using [Gettext](https://hexdocs.pm/gettext), your module gains a set of macros for translations, for example: - import SafiraWeb.Gettext + use Gettext, backend: SafiraWeb.Gettext # Simple translation gettext("Here is the string to translate") @@ -20,5 +20,5 @@ defmodule SafiraWeb.Gettext do See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. """ - use Gettext, otp_app: :safira + use Gettext.Backend, otp_app: :safira end diff --git a/lib/safira_web/helpers.ex b/lib/safira_web/helpers.ex index 42ca9d4d..925a32a0 100644 --- a/lib/safira_web/helpers.ex +++ b/lib/safira_web/helpers.ex @@ -2,8 +2,8 @@ defmodule SafiraWeb.Helpers do @moduledoc """ Helper functions for web views. """ - alias Timex.Format.DateTime.Formatters.Relative alias Safira.Constants + alias Timex.Format.DateTime.Formatters.Relative require Timex.Translator diff --git a/mix.exs b/mix.exs index 4a5c8a67..67810414 100644 --- a/mix.exs +++ b/mix.exs @@ -113,7 +113,7 @@ defmodule Safira.MixProject do "esbuild safira --minify", "phx.digest" ], - lint: ["credo -C default"] + lint: ["credo --all --strict"] ] end end diff --git a/test/safira_web/controllers/error_html_test.exs b/test/safira_web/controllers/error_html_test.exs index 517371a0..1cf319c2 100644 --- a/test/safira_web/controllers/error_html_test.exs +++ b/test/safira_web/controllers/error_html_test.exs @@ -5,10 +5,10 @@ defmodule SafiraWeb.ErrorHTMLTest do import Phoenix.Template test "renders 404.html" do - assert render_to_string(SafiraWeb.ErrorHTML, "404", "html", []) == "Not Found" + assert render_to_string(SafiraWeb.ErrorHTML, "404", "html", []) == "Not Found\n" end test "renders 500.html" do - assert render_to_string(SafiraWeb.ErrorHTML, "500", "html", []) == "Internal Server Error" + assert render_to_string(SafiraWeb.ErrorHTML, "500", "html", []) == "Internal Server Error\n" end end diff --git a/test/safira_web/controllers/user_session_controller_test.exs b/test/safira_web/controllers/user_session_controller_test.exs index 6c4403a2..e67ab968 100644 --- a/test/safira_web/controllers/user_session_controller_test.exs +++ b/test/safira_web/controllers/user_session_controller_test.exs @@ -2,8 +2,11 @@ defmodule SafiraWeb.UserSessionControllerTest do use SafiraWeb.ConnCase, async: true import Safira.AccountsFixtures + alias Safira.Constants setup do + Constants.set("registrations_open", "true") + Constants.set("start_time", "2024-09-29T17:57:00Z") %{user: user_fixture()} end From dce8fe8f955ad7951b6b21f9cf804adac44cd0e5 Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Mon, 11 Nov 2024 10:10:47 +0000 Subject: [PATCH 08/16] fix: compilation --- lib/safira_web/plugs/user_roles.ex | 2 +- lib/safira_web/router.ex | 10 +++++----- test/safira_web/live/user_registration_live_test.exs | 7 +++++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/safira_web/plugs/user_roles.ex b/lib/safira_web/plugs/user_roles.ex index 09d4f2da..4c441d26 100644 --- a/lib/safira_web/plugs/user_roles.ex +++ b/lib/safira_web/plugs/user_roles.ex @@ -4,7 +4,7 @@ defmodule SafiraWeb.UserRoles do """ use SafiraWeb, :verified_routes - import SafiraWeb.Gettext + use Gettext, backend: SafiraWeb.Gettext import Plug.Conn import Phoenix.Controller diff --git a/lib/safira_web/router.ex b/lib/safira_web/router.ex index 4210936b..fd2f5239 100644 --- a/lib/safira_web/router.ex +++ b/lib/safira_web/router.ex @@ -76,7 +76,11 @@ defmodule SafiraWeb.Router do live "/scanner", ScannerLive.Index, :index scope "/app", App do - pipe_through [:require_attendee_user, :backoffice_enabled] + pipe_through [:require_attendee_user] + + live "/waiting", WaitingLive.Index, :index + + pipe_through [:backoffice_enabled] scope "/credential", CredentialLive do pipe_through [:require_no_credential] @@ -85,10 +89,6 @@ defmodule SafiraWeb.Router do pipe_through [:require_credential] - live "/waiting", WaitingLive.Index, :index - - pipe_through [:require_credential] - live "/", HomeLive.Index, :index live "/credential", CredentialLive.Index, :index diff --git a/test/safira_web/live/user_registration_live_test.exs b/test/safira_web/live/user_registration_live_test.exs index 866c8600..2943b42e 100644 --- a/test/safira_web/live/user_registration_live_test.exs +++ b/test/safira_web/live/user_registration_live_test.exs @@ -4,6 +4,13 @@ defmodule SafiraWeb.UserRegistrationLiveTest do import Phoenix.LiveViewTest import Safira.AccountsFixtures + alias Safira.Constants + + setup do + Constants.set("start_time", "2024-09-29T15:00:00Z") + :ok + end + describe "Registration page" do test "renders registration page", %{conn: conn} do {:ok, _lv, html} = live(conn, ~p"/users/register") From 68226e720a93b6e5dbc66340357ccb92815bd3a7 Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Thu, 14 Nov 2024 12:40:34 +0000 Subject: [PATCH 09/16] feat: live updates --- assets/js/hooks/countdown.js | 9 +- lib/safira/accounts/roles/permissions.ex | 4 +- lib/safira/event.ex | 68 +++++++++++++++ lib/safira_web/components/table.ex | 2 +- lib/safira_web/config.ex | 82 ++++++++++--------- lib/safira_web/helpers.ex | 19 ----- lib/safira_web/live/app/waiting_live/index.ex | 34 ++++++-- lib/safira_web/live/auth/user_login_live.ex | 4 +- .../backoffice/event_live/form_component.ex | 13 ++- .../live/backoffice/event_live/index.ex | 14 ++-- lib/safira_web/plugs/event_roles.ex | 21 ++--- priv/repo/seeds/accounts.exs | 2 +- priv/repo/seeds/constants.exs | 2 +- 13 files changed, 172 insertions(+), 102 deletions(-) diff --git a/assets/js/hooks/countdown.js b/assets/js/hooks/countdown.js index 515af443..78c18e96 100644 --- a/assets/js/hooks/countdown.js +++ b/assets/js/hooks/countdown.js @@ -1,6 +1,7 @@ export const Countdown = { mounted() { const timeReceived = (start_time) => { + console.log(start_time); if(this.clock !== undefined) { clearInterval(this.clock); } @@ -17,11 +18,13 @@ export const Countdown = { clearInterval(this.clock); window.location.reload(); } - }, 1000); + }, 100); }; - window.addEventListener("phx:highlight", (e) => - timeReceived(new Date(e.detail.start_time).getTime())); + window.addEventListener("phx:highlight", (e) => { + console.log("Highlight"); + timeReceived(new Date(e.detail.start_time).getTime()) + }); } } diff --git a/lib/safira/accounts/roles/permissions.ex b/lib/safira/accounts/roles/permissions.ex index 70e09eca..92db3f58 100644 --- a/lib/safira/accounts/roles/permissions.ex +++ b/lib/safira/accounts/roles/permissions.ex @@ -13,11 +13,11 @@ defmodule Safira.Accounts.Roles.Permissions do "purchases" => ["show", "redeem", "refund"], "badges" => ["show", "edit", "delete", "give", "revoke", "give_without_restrictions"], "minigames" => ["show", "edit", "simulate"], + "event" => ["show", "edit"], "spotlights" => ["edit"], "schedule" => ["edit"], "statistics" => ["show"], - "mailer" => ["send"], - "event" => ["edit"] + "mailer" => ["send"] } end diff --git a/lib/safira/event.ex b/lib/safira/event.ex index fb1bad0a..73c67b0c 100644 --- a/lib/safira/event.ex +++ b/lib/safira/event.ex @@ -4,6 +4,74 @@ defmodule Safira.Event do """ alias Safira.Constants + @pubsub Safira.PubSub + + @doc """ + Returns whether the registrations for the event are open + + ## Examples + + iex> registrations_open?() + false + """ + def registrations_open? do + case Constants.get("registrations_open") do + {:ok, registrations_open} -> + case String.downcase(registrations_open) do + "true" -> true + _ -> false + end + + _ -> + false + end + end + + def change_registrations_open(registrations_open) do + Constants.set( + "registrations_open", + if registrations_open do + "true" + else + "false" + end + ) + end + + def get_event_start_time! do + with {:ok, start_time_str} <- Constants.get("start_time") do + with {:ok, start_time, _} <- DateTime.from_iso8601(start_time_str) do + start_time + end + end + end + + def change_event_start_time(start_time) do + Constants.set("start_time", DateTime.to_iso8601(start_time)) + broadcast_start_time_update("start_time", start_time) + end + + @doc """ + Subscribes the caller to the start time's updates. + + ## Examples + + iex> subscribe_to_start_time_update("start_time") + :ok + """ + def subscribe_to_start_time_update(config) do + Phoenix.PubSub.subscribe(@pubsub, config) + end + + defp broadcast_start_time_update(config, value) do + Phoenix.PubSub.broadcast(@pubsub, "start_time", {config, value}) + end + + def event_started? do + start_time = get_event_start_time!() + DateTime.compare(start_time, DateTime.utc_now()) == :lt + end + @doc """ Returns the event's start date. If the date is not set, it will be set to today's date by default. diff --git a/lib/safira_web/components/table.ex b/lib/safira_web/components/table.ex index 557c8ee7..49c6842d 100644 --- a/lib/safira_web/components/table.ex +++ b/lib/safira_web/components/table.ex @@ -5,7 +5,7 @@ defmodule SafiraWeb.Components.Table do use Phoenix.Component alias Plug.Conn.Query - use Gettext, backend: SafiraWeb.Gettexttext + use Gettext, backend: SafiraWeb.Gettext import SafiraWeb.CoreComponents attr :id, :string, required: true diff --git a/lib/safira_web/config.ex b/lib/safira_web/config.ex index 30d398c7..da7f33b8 100644 --- a/lib/safira_web/config.ex +++ b/lib/safira_web/config.ex @@ -3,45 +3,51 @@ defmodule SafiraWeb.Config do Web configuration for the app. """ + alias Safira.Event + def app_pages do - [ - %{ - key: :badgedex, - title: "Badgedex", - icon: "hero-check-badge", - url: "/app/badgedex" - }, - %{ - key: :wheel, - title: "Wheel", - icon: "hero-circle-stack", - url: "/app/wheel" - }, - %{ - key: :leaderboard, - title: "Leaderboard", - icon: "hero-trophy", - url: "/app/leaderboard" - }, - %{ - key: :store, - title: "Store", - icon: "hero-shopping-bag", - url: "/app/store" - }, - %{ - key: :vault, - title: "Vault", - icon: "hero-archive-box", - url: "/app/vault" - }, - %{ - key: :credential, - title: "Credential", - icon: "hero-ticket", - url: "/app/credential" - } - ] + if Event.event_started?() do + [ + %{ + key: :badgedex, + title: "Badgedex", + icon: "hero-check-badge", + url: "/app/badgedex" + }, + %{ + key: :wheel, + title: "Wheel", + icon: "hero-circle-stack", + url: "/app/wheel" + }, + %{ + key: :leaderboard, + title: "Leaderboard", + icon: "hero-trophy", + url: "/app/leaderboard" + }, + %{ + key: :store, + title: "Store", + icon: "hero-shopping-bag", + url: "/app/store" + }, + %{ + key: :vault, + title: "Vault", + icon: "hero-archive-box", + url: "/app/vault" + }, + %{ + key: :credential, + title: "Credential", + icon: "hero-ticket", + url: "/app/credential" + } + ] + else + [] + end end def backoffice_pages(user) do diff --git a/lib/safira_web/helpers.ex b/lib/safira_web/helpers.ex index 925a32a0..7971c6bd 100644 --- a/lib/safira_web/helpers.ex +++ b/lib/safira_web/helpers.ex @@ -2,7 +2,6 @@ defmodule SafiraWeb.Helpers do @moduledoc """ Helper functions for web views. """ - alias Safira.Constants alias Timex.Format.DateTime.Formatters.Relative require Timex.Translator @@ -162,22 +161,4 @@ defmodule SafiraWeb.Helpers do |> QRCodeEx.encode() |> QRCodeEx.svg(background_color: "#FFFFFF", color: "#04041C", width: 200) end - - def registrations_open? do - {:ok, registrations_open} = Constants.get("registrations_open") - string_to_bool(registrations_open) - end - - def get_start_time! do - {:ok, start_str} = Constants.get("start_time") - {:ok, start_time, _} = DateTime.from_iso8601(start_str) - start_time - end - - defp string_to_bool(str) do - case String.downcase(str) do - "true" -> true - _ -> false - end - end end diff --git a/lib/safira_web/live/app/waiting_live/index.ex b/lib/safira_web/live/app/waiting_live/index.ex index e406c5c2..6307e8e0 100644 --- a/lib/safira_web/live/app/waiting_live/index.ex +++ b/lib/safira_web/live/app/waiting_live/index.ex @@ -1,30 +1,48 @@ defmodule SafiraWeb.App.WaitingLive.Index do - use SafiraWeb, :live_view + use SafiraWeb, :app_view - alias SafiraWeb.Helpers + alias Safira.Event @impl true def render(assigns) do ~H"""
-

Countdown Timer

-
+ +

+ You are registered for SEI'25! +

+

We are almost ready

+
+
""" end @impl true def mount(_params, _session, socket) do - start_time = Helpers.get_start_time!() - - if DateTime.compare(start_time, DateTime.utc_now()) == :lt do + if Event.event_started?() do {:ok, socket |> push_navigate(to: ~p"/app")} else + if connected?(socket) do + Event.subscribe_to_start_time_update("start_time") + end + {:ok, socket - |> push_event("highlight", %{start_time: start_time})} + |> push_event("highlight", %{start_time: Event.get_event_start_time!()})} end end + + @impl true + def handle_info({"start_time", value}, socket) do + {:noreply, + socket + |> push_event("highlight", %{start_time: value})} + end end diff --git a/lib/safira_web/live/auth/user_login_live.ex b/lib/safira_web/live/auth/user_login_live.ex index 92400086..cae73f66 100644 --- a/lib/safira_web/live/auth/user_login_live.ex +++ b/lib/safira_web/live/auth/user_login_live.ex @@ -1,6 +1,8 @@ defmodule SafiraWeb.UserLoginLive do use SafiraWeb, :live_view + alias Safira.Event + def render(assigns) do ~H"""
@@ -45,7 +47,7 @@ defmodule SafiraWeb.UserLoginLive do email = Phoenix.Flash.get(socket.assigns.flash, :email) form = to_form(%{"email" => email}, as: "user") - {:ok, assign(socket, form: form) |> assign(registrations_open: registrations_open?()), + {:ok, assign(socket, form: form) |> assign(registrations_open: Event.registrations_open?()), temporary_assigns: [form: form]} end end diff --git a/lib/safira_web/live/backoffice/event_live/form_component.ex b/lib/safira_web/live/backoffice/event_live/form_component.ex index b50f9d66..c734d513 100644 --- a/lib/safira_web/live/backoffice/event_live/form_component.ex +++ b/lib/safira_web/live/backoffice/event_live/form_component.ex @@ -3,7 +3,7 @@ defmodule SafiraWeb.Backoffice.EventLive.FormComponent do import SafiraWeb.Components.Forms - alias Safira.Constants + alias Safira.Event @impl true def render(assigns) do @@ -61,8 +61,8 @@ defmodule SafiraWeb.Backoffice.EventLive.FormComponent do end def handle_event("save", params, socket) do - Constants.set("registrations_open", params["registrations_open"]) - Constants.set("start_time", DateTime.to_iso8601(parse_date(params["start_time"]))) + Event.change_registrations_open(string_to_bool(params["registrations_open"])) + Event.change_event_start_time(parse_date(params["start_time"])) {:noreply, socket @@ -74,4 +74,11 @@ defmodule SafiraWeb.Backoffice.EventLive.FormComponent do {:ok, date, _} = DateTime.from_iso8601("#{date_str}:00Z") date end + + defp string_to_bool(str) do + case String.downcase(str) do + "true" -> true + _ -> false + end + end end diff --git a/lib/safira_web/live/backoffice/event_live/index.ex b/lib/safira_web/live/backoffice/event_live/index.ex index 142ecf7a..58942c67 100644 --- a/lib/safira_web/live/backoffice/event_live/index.ex +++ b/lib/safira_web/live/backoffice/event_live/index.ex @@ -1,12 +1,14 @@ defmodule SafiraWeb.Backoffice.EventLive.Index do use SafiraWeb, :backoffice_view - alias SafiraWeb.Helpers + alias Safira.Event + + on_mount {SafiraWeb.StaffRoles, show: %{"event" => ["show"]}, edit: %{"event" => ["edit"]}} @impl true def mount(_params, _session, socket) do - registrations_open = Helpers.registrations_open?() - start_time = Helpers.get_start_time!() |> parse_date() + registrations_open = Event.registrations_open?() + start_time = Event.get_event_start_time!() form = to_form(%{"registrations_open" => registrations_open, "start_time" => start_time}) {:ok, @@ -19,10 +21,4 @@ defmodule SafiraWeb.Backoffice.EventLive.Index do def handle_params(_params, _url, socket) do {:noreply, socket} end - - defp parse_date(date) do - base_str = DateTime.to_iso8601(date) - len = String.length(base_str) - String.slice(base_str, 0, len - 4) - end end diff --git a/lib/safira_web/plugs/event_roles.ex b/lib/safira_web/plugs/event_roles.ex index 41e6a559..2a23eeec 100644 --- a/lib/safira_web/plugs/event_roles.ex +++ b/lib/safira_web/plugs/event_roles.ex @@ -5,17 +5,13 @@ defmodule SafiraWeb.EventRoles do import Plug.Conn import Phoenix.Controller - alias Safira.Constants - - alias SafiraWeb.Helpers + alias Safira.Event @doc """ Used to check if registrations have opened, so users can register / log in """ def registrations_open(conn, _opts) do - {:ok, open} = Constants.get("registrations_open") - - if open do + if Event.registrations_open?() do conn else raise %Ecto.NoResultsError{} @@ -26,19 +22,12 @@ defmodule SafiraWeb.EventRoles do Used to check if attendees can already access the backoffice """ def backoffice_enabled(conn, _opts) do - attendees_allowed = - not_in_future?(Helpers.get_start_time!()) - - if conn.assigns.current_user.type == :attendee and not attendees_allowed do + if Event.event_started?() do conn - |> redirect(to: ~p"/app/waiting") - |> halt() else conn + |> redirect(to: ~p"/app/waiting") + |> halt() end end - - defp not_in_future?(time) do - DateTime.compare(time, DateTime.utc_now()) == :lt - end end diff --git a/priv/repo/seeds/accounts.exs b/priv/repo/seeds/accounts.exs index 55e3849e..da938fd0 100644 --- a/priv/repo/seeds/accounts.exs +++ b/priv/repo/seeds/accounts.exs @@ -14,7 +14,7 @@ defmodule Safira.Repo.Seeds.Accounts do [] -> seed_attendees(attendee_names) _ -> - Mix.shell().error("Found staff accounts, aborting seeding staffs.") + Mix.shell().error("Found attendee accounts, aborting seeding attendees.") end case Accounts.list_staffs() do diff --git a/priv/repo/seeds/constants.exs b/priv/repo/seeds/constants.exs index 71530076..159a40ac 100644 --- a/priv/repo/seeds/constants.exs +++ b/priv/repo/seeds/constants.exs @@ -3,7 +3,7 @@ defmodule Safira.Repo.Seeds.Constants do def run do Constants.set("registrations_open", "true") - Constants.set("start_time", "2024-09-29T17:57:00Z") + Constants.set("start_time", "2025-09-29T17:57:00Z") end end From 0f07176a4417fae029b9da00085c902abfcbe1a8 Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Mon, 18 Nov 2024 13:53:59 +0000 Subject: [PATCH 10/16] chore: suggestions --- lib/safira_web/helpers.ex | 12 +++++++ .../backoffice/event_live/form_component.ex | 33 +++++++++---------- priv/repo/seeds/constants.exs | 2 +- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/lib/safira_web/helpers.ex b/lib/safira_web/helpers.ex index 7971c6bd..9b95998c 100644 --- a/lib/safira_web/helpers.ex +++ b/lib/safira_web/helpers.ex @@ -161,4 +161,16 @@ defmodule SafiraWeb.Helpers do |> QRCodeEx.encode() |> QRCodeEx.svg(background_color: "#FFFFFF", color: "#04041C", width: 200) end + + def parse_date(date_str) do + {:ok, date, _} = DateTime.from_iso8601("#{date_str}:00Z") + date + end + + def string_to_bool(str) do + case String.downcase(str) do + "true" -> true + _ -> false + end + end end diff --git a/lib/safira_web/live/backoffice/event_live/form_component.ex b/lib/safira_web/live/backoffice/event_live/form_component.ex index c734d513..9d870077 100644 --- a/lib/safira_web/live/backoffice/event_live/form_component.ex +++ b/lib/safira_web/live/backoffice/event_live/form_component.ex @@ -5,6 +5,8 @@ defmodule SafiraWeb.Backoffice.EventLive.FormComponent do alias Safira.Event + alias SafiraWeb.Helpers + @impl true def render(assigns) do ~H""" @@ -61,24 +63,19 @@ defmodule SafiraWeb.Backoffice.EventLive.FormComponent do end def handle_event("save", params, socket) do - Event.change_registrations_open(string_to_bool(params["registrations_open"])) - Event.change_event_start_time(parse_date(params["start_time"])) - - {:noreply, - socket - |> put_flash(:info, "Event settings updated successfully") - |> push_patch(to: socket.assigns.patch)} - end - - defp parse_date(date_str) do - {:ok, date, _} = DateTime.from_iso8601("#{date_str}:00Z") - date - end - - defp string_to_bool(str) do - case String.downcase(str) do - "true" -> true - _ -> false + with {:ok, _registrations_open} <- + Event.change_registrations_open(Helpers.string_to_bool(params["registrations_open"])), + {:ok, _start_time} <- + Event.change_event_start_time(Helpers.parse_date(params["start_time"])) do + {:noreply, + socket + |> put_flash(:info, "Event settings updated successfully") + |> push_patch(to: socket.assigns.patch)} + else + {:error, _reason} -> + {:noreply, + socket + |> put_flash(:error, "Failed to save event settings")} end end end diff --git a/priv/repo/seeds/constants.exs b/priv/repo/seeds/constants.exs index 159a40ac..71530076 100644 --- a/priv/repo/seeds/constants.exs +++ b/priv/repo/seeds/constants.exs @@ -3,7 +3,7 @@ defmodule Safira.Repo.Seeds.Constants do def run do Constants.set("registrations_open", "true") - Constants.set("start_time", "2025-09-29T17:57:00Z") + Constants.set("start_time", "2024-09-29T17:57:00Z") end end From 36a37763a04f918c52d1d5d4c0c31a150798e6fc Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Mon, 18 Nov 2024 14:13:14 +0000 Subject: [PATCH 11/16] feat: change layout --- assets/js/hooks/countdown.js | 1 - .../components/layouts/app.html.heex | 34 ++++++++++--------- lib/safira_web/live/app/waiting_live/index.ex | 4 +++ 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/assets/js/hooks/countdown.js b/assets/js/hooks/countdown.js index 78c18e96..5974dc18 100644 --- a/assets/js/hooks/countdown.js +++ b/assets/js/hooks/countdown.js @@ -22,7 +22,6 @@ export const Countdown = { }; window.addEventListener("phx:highlight", (e) => { - console.log("Highlight"); timeReceived(new Date(e.detail.start_time).getTime()) }); } diff --git a/lib/safira_web/components/layouts/app.html.heex b/lib/safira_web/components/layouts/app.html.heex index 5a213407..1dd8a592 100644 --- a/lib/safira_web/components/layouts/app.html.heex +++ b/lib/safira_web/components/layouts/app.html.heex @@ -1,20 +1,22 @@
- <.sidebar - current_user={@current_user} - pages={SafiraWeb.Config.app_pages()} - current_page={Map.get(assigns, :current_page, nil)} - background="bg-primaryDark" - border="border-darkShade" - logo_padding="px-16 pt-8 pb-4" - logo_images={%{light: "/images/sei.svg", dark: "/images/sei.svg"}} - logo_url={~p"/app/"} - user_dropdown_name_color="text-light" - user_dropdown_handle_color="text-lightMuted" - user_dropdown_icon_color="text-lightShade" - link_class="px-3 group flex items-center py-2 text-sm font-medium rounded-md transition-colors" - link_active_class="bg-light text-primaryDark" - link_inactive_class="hover:bg-primary-500/10 text-light" - /> + <%= if Map.get(assigns, :event_started, true) do %> + <.sidebar + current_user={@current_user} + pages={SafiraWeb.Config.app_pages()} + current_page={Map.get(assigns, :current_page, nil)} + background="bg-primaryDark" + border="border-darkShade" + logo_padding="px-16 pt-8 pb-4" + logo_images={%{light: "/images/sei.svg", dark: "/images/sei.svg"}} + logo_url={~p"/app/"} + user_dropdown_name_color="text-light" + user_dropdown_handle_color="text-lightMuted" + user_dropdown_icon_color="text-lightShade" + link_class="px-3 group flex items-center py-2 text-sm font-medium rounded-md transition-colors" + link_active_class="bg-light text-primaryDark" + link_inactive_class="hover:bg-primary-500/10 text-light" + /> + <% end %>
+ <.link class="text-center block mt-8 underline" href="/users/log_out" method="delete"> + Sign out +
""" end @@ -35,6 +38,7 @@ defmodule SafiraWeb.App.WaitingLive.Index do {:ok, socket + |> assign(:event_started, false) |> push_event("highlight", %{start_time: Event.get_event_start_time!()})} end end From 0f0524748d7be576eda1ded98fea95ba23c33bb7 Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Mon, 18 Nov 2024 14:18:36 +0000 Subject: [PATCH 12/16] test: fix --- test/safira_web/live/user_registration_live_test.exs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/safira_web/live/user_registration_live_test.exs b/test/safira_web/live/user_registration_live_test.exs index 2943b42e..b3a63866 100644 --- a/test/safira_web/live/user_registration_live_test.exs +++ b/test/safira_web/live/user_registration_live_test.exs @@ -8,6 +8,7 @@ defmodule SafiraWeb.UserRegistrationLiveTest do setup do Constants.set("start_time", "2024-09-29T15:00:00Z") + Constants.set("registrations_open", "true") :ok end From 8d55e9985dc340f86f07d7189c68c13b3b90e283 Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Mon, 18 Nov 2024 14:22:09 +0000 Subject: [PATCH 13/16] test: fix --- test/safira_web/live/user_registration_live_test.exs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/safira_web/live/user_registration_live_test.exs b/test/safira_web/live/user_registration_live_test.exs index b3a63866..2943b42e 100644 --- a/test/safira_web/live/user_registration_live_test.exs +++ b/test/safira_web/live/user_registration_live_test.exs @@ -8,7 +8,6 @@ defmodule SafiraWeb.UserRegistrationLiveTest do setup do Constants.set("start_time", "2024-09-29T15:00:00Z") - Constants.set("registrations_open", "true") :ok end From 57ab0113c3f59fde3101b984b1d5bc09ae898d5c Mon Sep 17 00:00:00 2001 From: Rui Oliveira <70754369+ruioliveira02@users.noreply.github.com> Date: Wed, 20 Nov 2024 09:49:58 +0000 Subject: [PATCH 14/16] chore: suggestions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mário Rodrigues <93675410+MarioRodrigues10@users.noreply.github.com> --- assets/js/hooks/countdown.js | 75 +++++++++++++++++------------------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/assets/js/hooks/countdown.js b/assets/js/hooks/countdown.js index 5974dc18..8a0195c6 100644 --- a/assets/js/hooks/countdown.js +++ b/assets/js/hooks/countdown.js @@ -1,55 +1,50 @@ export const Countdown = { mounted() { - const timeReceived = (start_time) => { - console.log(start_time); - if(this.clock !== undefined) { - clearInterval(this.clock); + let countdownInterval = null; + + const startCountdown = (startTime) => { + if (countdownInterval) { + clearInterval(countdownInterval); + } + + const textElement = document.getElementById("seconds-remaining"); + if (!textElement) { + console.warn("Countdown element not found!"); + return; } - this.clock = setInterval(() => { - const now = new Date().getTime(); - const seconds_left = Math.round((start_time - now) / 1000); - const text_element = document.getElementById("seconds-remaining"); + countdownInterval = setInterval(() => { + const now = Date.now(); + const secondsLeft = Math.round((startTime - now) / 1000); - if(seconds_left >= 0) { - text_element.innerHTML = formatTimeRemaining(seconds_left); + if (secondsLeft >= 0) { + textElement.textContent = formatTime(secondsLeft); } else { - text_element.innerText = "00"; - clearInterval(this.clock); + clearInterval(countdownInterval); + textElement.textContent = "00"; window.location.reload(); } }, 100); }; window.addEventListener("phx:highlight", (e) => { - timeReceived(new Date(e.detail.start_time).getTime()) + const startTime = new Date(e.detail.start_time).getTime(); + startCountdown(startTime); }); } -} - -function formatTimeRemaining(seconds) { - const timeUnits = { - days: Math.floor(seconds / (24 * 60 * 60)), - hours: Math.floor((seconds % (24 * 60 * 60)) / 3600), - minutes: Math.floor((seconds % 3600) / 60), - seconds: seconds % 60 - }; - - // Format units to two digits except for days - const formattedTime = { - hours: String(timeUnits.hours).padStart(2, '0'), - minutes: String(timeUnits.minutes).padStart(2, '0'), - seconds: String(timeUnits.seconds).padStart(2, '0') - }; - - if (timeUnits.days > 0) { - return `${timeUnits.days} days, ${formattedTime.hours}:${formattedTime.minutes}:${formattedTime.seconds}`; - } - if (timeUnits.hours > 0) { - return `${formattedTime.hours}:${formattedTime.minutes}:${formattedTime.seconds}`; - } - if (timeUnits.minutes > 0) { - return `${formattedTime.minutes}:${formattedTime.seconds}`; - } - return `${timeUnits.seconds}`; +}; + +function formatTime(totalSeconds) { + const dayToSeconds = 86400; + const days = Math.floor(totalSeconds / dayToSeconds); + const hours = Math.floor((totalSeconds % dayToSeconds) / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + + const formattedTime = [ + days > 0 ? `${days} days` : null, + `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}` + ].filter(Boolean).join(", "); + + return formattedTime; } \ No newline at end of file From 9d5000387dcf55bda69c1471ffbe0fdbe59be5cc Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Wed, 27 Nov 2024 19:12:16 +0000 Subject: [PATCH 15/16] fix: edit --- lib/safira/event.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/safira/event.ex b/lib/safira/event.ex index 73c67b0c..796d4fb9 100644 --- a/lib/safira/event.ex +++ b/lib/safira/event.ex @@ -47,8 +47,9 @@ defmodule Safira.Event do end def change_event_start_time(start_time) do - Constants.set("start_time", DateTime.to_iso8601(start_time)) + result = Constants.set("start_time", DateTime.to_iso8601(start_time)) broadcast_start_time_update("start_time", start_time) + result end @doc """ From 276ed748caed9e56eb8c56833fff73307b704fca Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Wed, 27 Nov 2024 21:37:52 +0000 Subject: [PATCH 16/16] chore: suggestions --- .../live/backoffice/event_live/form_component.ex | 4 ++-- lib/safira_web/live/backoffice/event_live/index.html.heex | 8 +++++--- lib/safira_web/router.ex | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/safira_web/live/backoffice/event_live/form_component.ex b/lib/safira_web/live/backoffice/event_live/form_component.ex index 9d870077..f24832c7 100644 --- a/lib/safira_web/live/backoffice/event_live/form_component.ex +++ b/lib/safira_web/live/backoffice/event_live/form_component.ex @@ -24,7 +24,7 @@ defmodule SafiraWeb.Backoffice.EventLive.FormComponent do >
- <.field field={@form[:registrations_open]} type="checkbox" label="Registrations Open" /> + <.field field={@form[:registrations_open]} type="switch" label="Registrations Open" /> <.field field={@form[:start_time]} type="datetime-local" @@ -70,7 +70,7 @@ defmodule SafiraWeb.Backoffice.EventLive.FormComponent do {:noreply, socket |> put_flash(:info, "Event settings updated successfully") - |> push_patch(to: socket.assigns.patch)} + |> push_navigate(to: socket.assigns.navigate)} else {:error, _reason} -> {:noreply, diff --git a/lib/safira_web/live/backoffice/event_live/index.html.heex b/lib/safira_web/live/backoffice/event_live/index.html.heex index cabba574..fda2a45c 100644 --- a/lib/safira_web/live/backoffice/event_live/index.html.heex +++ b/lib/safira_web/live/backoffice/event_live/index.html.heex @@ -6,8 +6,10 @@
-

<%= gettext("Registrations open:") %> <%= @form[:registrations_open].value %>

-

<%= gettext("Start Date/Time:") %> <%= @form[:start_time].value %>

+

+ <%= gettext("Registrations Open:") %> <%= @form[:registrations_open].value %> +

+

<%= gettext("Start Date/Time:") %> <%= @form[:start_time].value %>

<.modal @@ -22,6 +24,6 @@ title="Event Settings" action={@live_action} form={@form} - patch={~p"/dashboard/event"} + navigate={~p"/dashboard/event"} /> diff --git a/lib/safira_web/router.ex b/lib/safira_web/router.ex index fd2f5239..6e1988fd 100644 --- a/lib/safira_web/router.ex +++ b/lib/safira_web/router.ex @@ -52,6 +52,8 @@ defmodule SafiraWeb.Router do scope "/", SafiraWeb do pipe_through [:browser, :redirect_if_user_is_authenticated] + post "/users/log_in", UserSessionController, :create + live_session :redirect_if_user_is_authenticated, on_mount: [{SafiraWeb.UserAuth, :redirect_if_user_is_authenticated}] do live "/users/log_in", UserLoginLive, :new @@ -61,8 +63,6 @@ defmodule SafiraWeb.Router do pipe_through :registrations_open live "/users/register", UserRegistrationLive, :new end - - post "/users/log_in", UserSessionController, :create end scope "/", SafiraWeb do