add invites route
This commit is contained in:
		| @@ -4,8 +4,8 @@ defmodule Lokal.Invites do | |||||||
|   """ |   """ | ||||||
|  |  | ||||||
|   import Ecto.Query, warn: false |   import Ecto.Query, warn: false | ||||||
|   alias Lokal.{Accounts.User, Invites.Invite, Repo} |  | ||||||
|   alias Ecto.Changeset |   alias Ecto.Changeset | ||||||
|  |   alias Lokal.{Accounts.User, Invites.Invite, Repo} | ||||||
|  |  | ||||||
|   @invite_token_length 20 |   @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