add invites route
This commit is contained in:
		| @@ -4,8 +4,8 @@ defmodule Lokal.Invites do | ||||
|   """ | ||||
|  | ||||
|   import Ecto.Query, warn: false | ||||
|   alias Lokal.{Accounts.User, Invites.Invite, Repo} | ||||
|   alias Ecto.Changeset | ||||
|   alias Lokal.{Accounts.User, Invites.Invite, Repo} | ||||
|  | ||||
|   @invite_token_length 20 | ||||
|  | ||||
|   | ||||
							
								
								
									
										68
									
								
								lib/lokal_web/live/invite_live/form_component.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								lib/lokal_web/live/invite_live/form_component.ex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| defmodule LokalWeb.InviteLive.FormComponent do | ||||
|   @moduledoc """ | ||||
|   Livecomponent that can update or create an Lokal.Invites.Invite | ||||
|   """ | ||||
|  | ||||
|   use LokalWeb, :live_component | ||||
|   alias Lokal.{Accounts.User, Invites, Invites.Invite} | ||||
|   alias Ecto.Changeset | ||||
|   alias Phoenix.LiveView.Socket | ||||
|  | ||||
|   @impl true | ||||
|   @spec update( | ||||
|           %{:invite => Invite.t(), :current_user => User.t(), optional(any) => any}, | ||||
|           Socket.t() | ||||
|         ) :: {:ok, Socket.t()} | ||||
|   def update(%{invite: invite} = assigns, socket) do | ||||
|     {:ok, socket |> assign(assigns) |> assign(:changeset, Invites.change_invite(invite))} | ||||
|   end | ||||
|  | ||||
|   @impl true | ||||
|   def handle_event( | ||||
|         "validate", | ||||
|         %{"invite" => invite_params}, | ||||
|         %{assigns: %{invite: invite}} = socket | ||||
|       ) do | ||||
|     {:noreply, socket |> assign(:changeset, invite |> Invites.change_invite(invite_params))} | ||||
|   end | ||||
|  | ||||
|   def handle_event("save", %{"invite" => invite_params}, %{assigns: %{action: action}} = socket) do | ||||
|     save_invite(socket, action, invite_params) | ||||
|   end | ||||
|  | ||||
|   defp save_invite( | ||||
|          %{assigns: %{current_user: current_user, invite: invite, return_to: return_to}} = socket, | ||||
|          :edit, | ||||
|          invite_params | ||||
|        ) do | ||||
|     socket = | ||||
|       case invite |> Invites.update_invite(invite_params, current_user) do | ||||
|         {:ok, %{name: invite_name}} -> | ||||
|           prompt = dgettext("prompts", "%{name} updated successfully", name: invite_name) | ||||
|           socket |> put_flash(:info, prompt) |> push_redirect(to: return_to) | ||||
|  | ||||
|         {:error, %Changeset{} = changeset} -> | ||||
|           socket |> assign(:changeset, changeset) | ||||
|       end | ||||
|  | ||||
|     {:noreply, socket} | ||||
|   end | ||||
|  | ||||
|   defp save_invite( | ||||
|          %{assigns: %{current_user: current_user, return_to: return_to}} = socket, | ||||
|          :new, | ||||
|          invite_params | ||||
|        ) do | ||||
|     socket = | ||||
|       case current_user |> Invites.create_invite(invite_params) do | ||||
|         {:ok, %{name: invite_name}} -> | ||||
|           prompt = dgettext("prompts", "%{name} created successfully", name: invite_name) | ||||
|           socket |> put_flash(:info, prompt) |> push_redirect(to: return_to) | ||||
|  | ||||
|         {:error, %Changeset{} = changeset} -> | ||||
|           socket |> assign(changeset: changeset) | ||||
|       end | ||||
|  | ||||
|     {:noreply, socket} | ||||
|   end | ||||
| end | ||||
							
								
								
									
										33
									
								
								lib/lokal_web/live/invite_live/form_component.html.heex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								lib/lokal_web/live/invite_live/form_component.html.heex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| <div> | ||||
|   <h2 class="mb-8 text-center title text-xl text-primary-600"> | ||||
|     <%= @title %> | ||||
|   </h2> | ||||
|   <.form | ||||
|     let={f} | ||||
|     for={@changeset} | ||||
|     id="invite-form" | ||||
|     class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center" | ||||
|     phx-target={@myself} | ||||
|     phx-change="validate" | ||||
|     phx-submit="save" | ||||
|   > | ||||
|     <%= if @changeset.action && not @changeset.valid? do %> | ||||
|       <div class="invalid-feedback col-span-3 text-center"> | ||||
|         <%= changeset_errors(@changeset) %> | ||||
|       </div> | ||||
|     <% end %> | ||||
|  | ||||
|     <%= label(f, :name, gettext("Name"), class: "title text-lg text-primary-600") %> | ||||
|     <%= text_input(f, :name, class: "input input-primary col-span-2") %> | ||||
|     <%= error_tag(f, :name, "col-span-3") %> | ||||
|  | ||||
|     <%= label(f, :uses_left, gettext("Uses left"), class: "title text-lg text-primary-600") %> | ||||
|     <%= number_input(f, :uses_left, min: 0, class: "input input-primary col-span-2") %> | ||||
|     <%= error_tag(f, :uses_left, "col-span-3") %> | ||||
|  | ||||
|     <%= submit(dgettext("actions", "Save"), | ||||
|       class: "mx-auto btn btn-primary col-span-3", | ||||
|       phx_disable_with: dgettext("prompts", "Saving...") | ||||
|     ) %> | ||||
|   </.form> | ||||
| </div> | ||||
							
								
								
									
										152
									
								
								lib/lokal_web/live/invite_live/index.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								lib/lokal_web/live/invite_live/index.ex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | ||||
| defmodule LokalWeb.InviteLive.Index do | ||||
|   @moduledoc """ | ||||
|   Liveview to show a Lokal.Invites.Invite index | ||||
|   """ | ||||
|  | ||||
|   use LokalWeb, :live_view | ||||
|   import LokalWeb.Components.{InviteCard, UserCard} | ||||
|   alias Lokal.{Accounts, Invites, Invites.Invite} | ||||
|   alias LokalWeb.{Endpoint, PageLive} | ||||
|   alias Phoenix.LiveView.JS | ||||
|  | ||||
|   @impl true | ||||
|   def mount(_params, session, socket) do | ||||
|     %{assigns: %{current_user: current_user}} = socket = socket |> assign_defaults(session) | ||||
|  | ||||
|     socket = | ||||
|       if current_user |> Map.get(:role) == :admin do | ||||
|         socket |> display_invites() | ||||
|       else | ||||
|         prompt = dgettext("errors", "You are not authorized to view this page") | ||||
|         return_to = Routes.live_path(Endpoint, PageLive) | ||||
|         socket |> put_flash(:error, prompt) |> push_redirect(to: return_to) | ||||
|       end | ||||
|  | ||||
|     {:ok, socket} | ||||
|   end | ||||
|  | ||||
|   @impl true | ||||
|   def handle_params(params, _url, %{assigns: %{live_action: live_action}} = socket) do | ||||
|     {:noreply, socket |> apply_action(live_action, params)} | ||||
|   end | ||||
|  | ||||
|   defp apply_action(%{assigns: %{current_user: current_user}} = socket, :edit, %{"id" => id}) do | ||||
|     socket | ||||
|     |> assign(page_title: gettext("Edit Invite"), invite: Invites.get_invite!(id, current_user)) | ||||
|   end | ||||
|  | ||||
|   defp apply_action(socket, :new, _params) do | ||||
|     socket |> assign(page_title: gettext("New Invite"), invite: %Invite{}) | ||||
|   end | ||||
|  | ||||
|   defp apply_action(socket, :index, _params) do | ||||
|     socket |> assign(page_title: gettext("Invites"), invite: nil) | ||||
|   end | ||||
|  | ||||
|   @impl true | ||||
|   def handle_event( | ||||
|         "delete_invite", | ||||
|         %{"id" => id}, | ||||
|         %{assigns: %{current_user: current_user}} = socket | ||||
|       ) do | ||||
|     %{name: invite_name} = | ||||
|       id |> Invites.get_invite!(current_user) |> Invites.delete_invite!(current_user) | ||||
|  | ||||
|     prompt = dgettext("prompts", "%{name} deleted succesfully", name: invite_name) | ||||
|     {:noreply, socket |> put_flash(:info, prompt) |> display_invites()} | ||||
|   end | ||||
|  | ||||
|   def handle_event( | ||||
|         "set_unlimited", | ||||
|         %{"id" => id}, | ||||
|         %{assigns: %{current_user: current_user}} = socket | ||||
|       ) do | ||||
|     socket = | ||||
|       Invites.get_invite!(id, current_user) | ||||
|       |> Invites.update_invite(%{"uses_left" => nil}, current_user) | ||||
|       |> case do | ||||
|         {:ok, %{name: invite_name}} -> | ||||
|           prompt = dgettext("prompts", "%{name} updated succesfully", name: invite_name) | ||||
|           socket |> put_flash(:info, prompt) |> display_invites() | ||||
|  | ||||
|         {:error, changeset} -> | ||||
|           socket |> put_flash(:error, changeset |> changeset_errors()) | ||||
|       end | ||||
|  | ||||
|     {:noreply, socket} | ||||
|   end | ||||
|  | ||||
|   def handle_event( | ||||
|         "enable_invite", | ||||
|         %{"id" => id}, | ||||
|         %{assigns: %{current_user: current_user}} = socket | ||||
|       ) do | ||||
|     socket = | ||||
|       Invites.get_invite!(id, current_user) | ||||
|       |> Invites.update_invite(%{"uses_left" => nil, "disabled_at" => nil}, current_user) | ||||
|       |> case do | ||||
|         {:ok, %{name: invite_name}} -> | ||||
|           prompt = dgettext("prompts", "%{name} enabled succesfully", name: invite_name) | ||||
|           socket |> put_flash(:info, prompt) |> display_invites() | ||||
|  | ||||
|         {:error, changeset} -> | ||||
|           socket |> put_flash(:error, changeset |> changeset_errors()) | ||||
|       end | ||||
|  | ||||
|     {:noreply, socket} | ||||
|   end | ||||
|  | ||||
|   def handle_event( | ||||
|         "disable_invite", | ||||
|         %{"id" => id}, | ||||
|         %{assigns: %{current_user: current_user}} = socket | ||||
|       ) do | ||||
|     now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) | ||||
|  | ||||
|     socket = | ||||
|       Invites.get_invite!(id, current_user) | ||||
|       |> Invites.update_invite(%{"uses_left" => 0, "disabled_at" => now}, current_user) | ||||
|       |> case do | ||||
|         {:ok, %{name: invite_name}} -> | ||||
|           prompt = dgettext("prompts", "%{name} disabled succesfully", name: invite_name) | ||||
|           socket |> put_flash(:info, prompt) |> display_invites() | ||||
|  | ||||
|         {:error, changeset} -> | ||||
|           socket |> put_flash(:error, changeset |> changeset_errors()) | ||||
|       end | ||||
|  | ||||
|     {:noreply, socket} | ||||
|   end | ||||
|  | ||||
|   @impl true | ||||
|   def handle_event("copy_to_clipboard", _, socket) do | ||||
|     prompt = dgettext("prompts", "Copied to clipboard") | ||||
|     {:noreply, socket |> put_flash(:info, prompt)} | ||||
|   end | ||||
|  | ||||
|   @impl true | ||||
|   def handle_event( | ||||
|         "delete_user", | ||||
|         %{"id" => id}, | ||||
|         %{assigns: %{current_user: current_user}} = socket | ||||
|       ) do | ||||
|     %{email: user_email} = Accounts.get_user!(id) |> Accounts.delete_user!(current_user) | ||||
|  | ||||
|     prompt = dgettext("prompts", "%{name} deleted succesfully", name: user_email) | ||||
|  | ||||
|     {:noreply, socket |> put_flash(:info, prompt) |> display_invites()} | ||||
|   end | ||||
|  | ||||
|   defp display_invites(%{assigns: %{current_user: current_user}} = socket) do | ||||
|     invites = Invites.list_invites(current_user) | ||||
|     all_users = Accounts.list_all_users_by_role(current_user) | ||||
|  | ||||
|     admins = | ||||
|       all_users | ||||
|       |> Map.get(:admin, []) | ||||
|       |> Enum.reject(fn %{id: user_id} -> user_id == current_user.id end) | ||||
|  | ||||
|     users = all_users |> Map.get(:user, []) | ||||
|     socket |> assign(invites: invites, admins: admins, users: users) | ||||
|   end | ||||
| end | ||||
							
								
								
									
										156
									
								
								lib/lokal_web/live/invite_live/index.html.heex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								lib/lokal_web/live/invite_live/index.html.heex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | ||||
| <div class="w-full flex flex-col space-y-8 justify-center items-center"> | ||||
|   <h1 class="title text-2xl title-primary-500"> | ||||
|     <%= gettext("Invites") %> | ||||
|   </h1> | ||||
|  | ||||
|   <%= if @invites |> Enum.empty?() do %> | ||||
|     <h1 class="title text-xl text-primary-600"> | ||||
|       <%= gettext("No invites 😔") %> | ||||
|     </h1> | ||||
|  | ||||
|     <%= live_patch(dgettext("actions", "Invite someone new!"), | ||||
|       to: Routes.invite_index_path(Endpoint, :new), | ||||
|       class: "btn btn-primary" | ||||
|     ) %> | ||||
|   <% else %> | ||||
|     <%= live_patch(dgettext("actions", "Create Invite"), | ||||
|       to: Routes.invite_index_path(Endpoint, :new), | ||||
|       class: "btn btn-primary" | ||||
|     ) %> | ||||
|   <% end %> | ||||
|  | ||||
|   <div class="w-full flex flex-row flex-wrap justify-center items-center"> | ||||
|     <%= for invite <- @invites do %> | ||||
|       <.invite_card invite={invite}> | ||||
|         <:code_actions> | ||||
|           <form phx-submit="copy_to_clipboard"> | ||||
|             <button | ||||
|               type="submit" | ||||
|               class="mx-2 my-1 btn btn-primary" | ||||
|               phx-click={JS.dispatch("lokal:clipcopy", to: "#code-#{invite.id}")} | ||||
|             > | ||||
|               <%= dgettext("actions", "Copy to clipboard") %> | ||||
|             </button> | ||||
|           </form> | ||||
|         </:code_actions> | ||||
|         <%= live_patch to: Routes.invite_index_path(Endpoint, :edit, invite), | ||||
|                    class: "text-primary-600 link", | ||||
|                    data: [qa: "edit-#{invite.id}"] do %> | ||||
|           <i class="fa-fw fa-lg fas fa-edit"></i> | ||||
|         <% end %> | ||||
|  | ||||
|         <%= link to: "#", | ||||
|              class: "text-primary-600 link", | ||||
|              phx_click: "delete_invite", | ||||
|              phx_value_id: invite.id, | ||||
|              data: [ | ||||
|                confirm: | ||||
|                  dgettext("prompts", "Are you sure you want to delete the invite for %{name}?", | ||||
|                    name: invite.name | ||||
|                  ), | ||||
|                qa: "delete-#{invite.id}" | ||||
|              ] do %> | ||||
|           <i class="fa-fw fa-lg fas fa-trash"></i> | ||||
|         <% end %> | ||||
|  | ||||
|         <%= if invite.disabled_at |> is_nil() do %> | ||||
|           <a href="#" class="btn btn-primary" phx-click="disable_invite" phx-value-id={invite.id}> | ||||
|             <%= gettext("Disable") %> | ||||
|           </a> | ||||
|         <% else %> | ||||
|           <a href="#" class="btn btn-primary" phx-click="enable_invite" phx-value-id={invite.id}> | ||||
|             <%= gettext("Enable") %> | ||||
|           </a> | ||||
|         <% end %> | ||||
|  | ||||
|         <%= if invite.disabled_at |> is_nil() and not (invite.uses_left |> is_nil()) do %> | ||||
|           <a | ||||
|             href="#" | ||||
|             class="btn btn-primary" | ||||
|             phx-click="set_unlimited" | ||||
|             phx-value-id={invite.id} | ||||
|             data-confirm={ | ||||
|               dgettext("prompts", "Are you sure you want to make %{name} unlimited?", | ||||
|                 name: invite.name | ||||
|               ) | ||||
|             } | ||||
|           > | ||||
|             <%= gettext("Set Unlimited") %> | ||||
|           </a> | ||||
|         <% end %> | ||||
|       </.invite_card> | ||||
|     <% end %> | ||||
|   </div> | ||||
|  | ||||
|   <%= unless @admins |> Enum.empty?() do %> | ||||
|     <hr class="hr" /> | ||||
|  | ||||
|     <h1 class="title text-2xl text-primary-600"> | ||||
|       <%= gettext("Admins") %> | ||||
|     </h1> | ||||
|  | ||||
|     <div class="w-full flex flex-row flex-wrap justify-center items-center"> | ||||
|       <%= for admin <- @admins do %> | ||||
|         <.user_card user={admin}> | ||||
|           <%= link to: "#", | ||||
|                class: "text-primary-600 link", | ||||
|                phx_click: "delete_user", | ||||
|                phx_value_id: admin.id, | ||||
|                data: [ | ||||
|                  confirm: | ||||
|                    dgettext( | ||||
|                      "prompts", | ||||
|                      "Are you sure you want to delete %{email}? This action is permanent!", | ||||
|                      email: admin.email | ||||
|                    ) | ||||
|                ] do %> | ||||
|             <i class="fa-fw fa-lg fas fa-trash"></i> | ||||
|           <% end %> | ||||
|         </.user_card> | ||||
|       <% end %> | ||||
|     </div> | ||||
|   <% end %> | ||||
|  | ||||
|   <%= unless @users |> Enum.empty?() do %> | ||||
|     <hr class="hr" /> | ||||
|  | ||||
|     <h1 class="title text-2xl text-primary-600"> | ||||
|       <%= gettext("Users") %> | ||||
|     </h1> | ||||
|  | ||||
|     <div class="w-full flex flex-row flex-wrap justify-center items-center"> | ||||
|       <%= for user <- @users do %> | ||||
|         <.user_card user={user}> | ||||
|           <%= link to: "#", | ||||
|                class: "text-primary-600 link", | ||||
|                phx_click: "delete_user", | ||||
|                phx_value_id: user.id, | ||||
|                data: [ | ||||
|                  confirm: | ||||
|                    dgettext( | ||||
|                      "prompts", | ||||
|                      "Are you sure you want to delete %{email}? This action is permanent!", | ||||
|                      email: user.email | ||||
|                    ) | ||||
|                ] do %> | ||||
|             <i class="fa-fw fa-lg fas fa-trash"></i> | ||||
|           <% end %> | ||||
|         </.user_card> | ||||
|       <% end %> | ||||
|     </div> | ||||
|   <% end %> | ||||
| </div> | ||||
|  | ||||
| <%= if @live_action in [:new, :edit] do %> | ||||
|   <.modal return_to={Routes.invite_index_path(Endpoint, :index)}> | ||||
|     <.live_component | ||||
|       module={LokalWeb.InviteLive.FormComponent} | ||||
|       id={@invite.id || :new} | ||||
|       title={@page_title} | ||||
|       action={@live_action} | ||||
|       invite={@invite} | ||||
|       return_to={Routes.invite_index_path(Endpoint, :index)} | ||||
|       current_user={@current_user} | ||||
|     /> | ||||
|   </.modal> | ||||
| <% end %> | ||||
		Reference in New Issue
	
	Block a user