add invite link and registration validation
This commit is contained in:
		| @@ -1,18 +1,67 @@ | ||||
| defmodule CanneryWeb.UserRegistrationController do | ||||
|   use CanneryWeb, :controller | ||||
|  | ||||
|   alias Cannery.Accounts | ||||
|   alias Cannery.{Accounts, Invites} | ||||
|   alias Cannery.Accounts.User | ||||
|   alias CanneryWeb.UserAuth | ||||
|  | ||||
|   def new(conn, _params) do | ||||
|     changeset = Accounts.change_user_registration(%User{}) | ||||
|     render(conn, "new.html", changeset: changeset) | ||||
|   def new(conn, %{"invite" => invite_token}) do | ||||
|     invite = Invites.get_invite_by_token(invite_token) | ||||
|  | ||||
|     if invite do | ||||
|       conn |> render_new(invite) | ||||
|     else | ||||
|       conn | ||||
|       |> put_flash(:error, "Sorry, this invite was not found or expired") | ||||
|       |> redirect(to: Routes.home_path(CanneryWeb.Endpoint, :index)) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def create(conn, %{"user" => user_params}) do | ||||
|   def new(conn, _params) do | ||||
|     if Accounts.allow_registration?() do | ||||
|       conn |> render_new() | ||||
|     else | ||||
|       conn | ||||
|       |> put_flash(:error, "Sorry, public registration is disabled") | ||||
|       |> redirect(to: Routes.home_path(CanneryWeb.Endpoint, :index)) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   # renders new user registration page | ||||
|   defp render_new(conn, invite \\ nil) do | ||||
|     changeset = Accounts.change_user_registration(%User{}) | ||||
|     conn |> render("new.html", changeset: changeset, invite: invite) | ||||
|   end | ||||
|  | ||||
|   def create(conn, %{"user" => %{"invite_token" => invite_token}} = attrs) do | ||||
|     invite = Invites.get_invite_by_token(invite_token) | ||||
|  | ||||
|     if invite do | ||||
|       conn |> create_user(attrs, invite) | ||||
|     else | ||||
|       conn | ||||
|       |> put_flash(:error, "Sorry, this invite was not found or expired") | ||||
|       |> redirect(to: Routes.home_path(CanneryWeb.Endpoint, :index)) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def create(conn, attrs) do | ||||
|     if Accounts.allow_registration?() do | ||||
|       conn |> create_user(attrs) | ||||
|     else | ||||
|       conn | ||||
|       |> put_flash(:error, "Sorry, public registration is disabled") | ||||
|       |> redirect(to: Routes.home_path(CanneryWeb.Endpoint, :index)) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   defp create_user(conn, %{"user" => user_params}, invite \\ nil) do | ||||
|     case Accounts.register_user(user_params) do | ||||
|       {:ok, user} -> | ||||
|         unless invite |> is_nil() do | ||||
|           invite |> Invites.use_invite!() | ||||
|         end | ||||
|  | ||||
|         {:ok, _} = | ||||
|           Accounts.deliver_user_confirmation_instructions( | ||||
|             user, | ||||
| @@ -24,7 +73,7 @@ defmodule CanneryWeb.UserRegistrationController do | ||||
|         |> UserAuth.log_in_user(user) | ||||
|  | ||||
|       {:error, %Ecto.Changeset{} = changeset} -> | ||||
|         render(conn, "new.html", changeset: changeset) | ||||
|         render(conn, "new.html", changeset: changeset, invite: invite) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -37,7 +37,7 @@ defmodule CanneryWeb.InviteLive.FormComponent do | ||||
|  | ||||
|       {:error, %Ecto.Changeset{} = changeset} -> | ||||
|         {:noreply, assign(socket, :changeset, changeset)} | ||||
|     end | ||||
|       end | ||||
|   end | ||||
|  | ||||
|   defp save_invite(socket, :new, invite_params) do | ||||
|   | ||||
| @@ -1,14 +1,28 @@ | ||||
| <h2><%= @title %></h2> | ||||
| <h2 class="title text-xl text-primary-500"> | ||||
|   <%= @title %> | ||||
| </h2> | ||||
|  | ||||
| <%= f = form_for @changeset, "#", | ||||
|   id: "invite-form", | ||||
|   class: "grid grid-cols-3 justify-center items-center space-y-4", | ||||
|   phx_target: @myself, | ||||
|   phx_change: "validate", | ||||
|   phx_submit: "save" %> | ||||
|  | ||||
|   <%= label f, :name, class: "title text-lg text-primary-500" %> | ||||
|   <%= text_input f, :name, class: "input input-primary" %> | ||||
|   <%= error_tag f, :name %> | ||||
|   <%= text_input f, :name, class: "input input-primary col-span-2" %> | ||||
|   <span class="col-span-3"> | ||||
|     <%= error_tag f, :name %> | ||||
|   </span> | ||||
|  | ||||
|   <%= submit "Save", phx_disable_with: "Saving..." %> | ||||
|   <%= label f, :uses_left, class: "title text-lg text-primary-500" %> | ||||
|   <%= number_input f, :uses_left, min: 0, class: "input input-primary col-span-2" %> | ||||
|   <span class="col-span-3"> | ||||
|     <%= error_tag f, :uses_left %> | ||||
|   </span> | ||||
|  | ||||
|   <div class="flex flex-row justify-center items-center space-x-4 col-span-3"> | ||||
|     <%= submit "Save", class: "btn btn-primary", | ||||
|       phx_disable_with: "Saving..." %> | ||||
|   </div> | ||||
| </form> | ||||
|   | ||||
| @@ -1,46 +1,62 @@ | ||||
| defmodule CanneryWeb.InviteLive.Index do | ||||
|   use CanneryWeb, :live_view | ||||
|  | ||||
|   alias Cannery.Invites | ||||
|   alias Cannery.Invites.Invite | ||||
|   alias Cannery.{Invites} | ||||
|   alias Cannery.Invites.{Invite} | ||||
|  | ||||
|   @impl true | ||||
|   def mount(_params, session, socket) do | ||||
|     {:ok, socket |> assign_defaults(session) |> assign(invites: list_invites())} | ||||
|     {:ok, socket |> assign_defaults(session) |> display_invites()} | ||||
|   end | ||||
|  | ||||
|   @impl true | ||||
|   def handle_params(params, _url, socket) do | ||||
|     {:noreply, apply_action(socket, socket.assigns.live_action, params)} | ||||
|     {:noreply, socket |> apply_action(socket.assigns.live_action, params)} | ||||
|   end | ||||
|  | ||||
|   defp apply_action(socket, :edit, %{"id" => id}) do | ||||
|     socket | ||||
|     |> assign(:page_title, "Edit Invite") | ||||
|     |> assign(:invite, Invites.get_invite!(id)) | ||||
|     |> assign(page_title: "Edit Invite", invite: Invites.get_invite!(id)) | ||||
|   end | ||||
|  | ||||
|   defp apply_action(socket, :new, _params) do | ||||
|     socket | ||||
|     |> assign(:page_title, "New Invite") | ||||
|     |> assign(:invite, %Invite{}) | ||||
|     |> assign(page_title: "New Invite", invite: %Invite{}) | ||||
|   end | ||||
|  | ||||
|   defp apply_action(socket, :index, _params) do | ||||
|     socket | ||||
|     |> assign(:page_title, "Listing Invites") | ||||
|     |> assign(:invite, nil) | ||||
|     |> assign(page_title: "Listing Invites", invite: nil) | ||||
|   end | ||||
|  | ||||
|   @impl true | ||||
|   def handle_event("delete", %{"id" => id}, socket) do | ||||
|     invite = Invites.get_invite!(id) | ||||
|     {:ok, _} = Invites.delete_invite(invite) | ||||
|  | ||||
|     {:noreply, assign(socket, :invites, list_invites())} | ||||
|     {:noreply, socket |> display_invites()} | ||||
|   end | ||||
|  | ||||
|   defp list_invites do | ||||
|     Invites.list_invites() | ||||
|   def handle_event("set_unlimited", %{"id" => id}, socket) do | ||||
|     id |> Invites.get_invite!() |> Invites.update_invite(%{"uses_left" => nil}) | ||||
|     {:noreply, socket |> display_invites()} | ||||
|   end | ||||
|  | ||||
|   def handle_event("enable", %{"id" => id}, socket) do | ||||
|     attrs = %{"uses_left" => nil, "disabled_at" => nil} | ||||
|     id |> Invites.get_invite!() |> Invites.update_invite(attrs) | ||||
|     {:noreply, socket |> display_invites()} | ||||
|   end | ||||
|  | ||||
|   def handle_event("disable", %{"id" => id}, socket) do | ||||
|     now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) | ||||
|     attrs = %{"uses_left" => 0, "disabled_at" => now} | ||||
|     id |> Invites.get_invite!() |> Invites.update_invite(attrs) | ||||
|     {:noreply, socket |> display_invites()} | ||||
|   end | ||||
|  | ||||
|   # redisplays invites to socket | ||||
|   defp display_invites(socket) do | ||||
|     invites = Invites.list_invites() | ||||
|     socket |> assign(:invites, invites) | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -1,4 +1,82 @@ | ||||
| <h1>Listing Invites</h1> | ||||
| <div class="flex flex-col space-y-8 justify-center items-center"> | ||||
|   <h1 class="title text-2xl title-primary-500"> | ||||
|     Listing Invites | ||||
|   </h1> | ||||
|  | ||||
|   <%= if @invites |> Enum.empty?() do %> | ||||
|     <div class="flex flex-col space-y-4 justify-center items-center"> | ||||
|       <h1 class="title text-xl text-primary-500"> | ||||
|         No invites 😔 | ||||
|       </h1> | ||||
|  | ||||
|       <%= live_patch to: Routes.invite_index_path(@socket, :new), | ||||
|         class: "btn btn-primary" do %> | ||||
|         Invite someone new! | ||||
|       <% end %> | ||||
|     </div> | ||||
|   <% else %> | ||||
|     <%= live_patch to: Routes.invite_index_path(@socket, :new), | ||||
|       class: "btn btn-primary" do %> | ||||
|       Invite | ||||
|     <% end %> | ||||
|   <% end %> | ||||
|  | ||||
|   <div class="flex flex-row flex-wrap space-x-4 space-y-4"> | ||||
|     <%= for invite <- @invites do %> | ||||
|       <div class="px-8 py-4 flex flex-col justify-center items-center space-y-4 | ||||
|         border border-gray-400 rounded-lg shadow-lg hover:shadow-md"> | ||||
|         <h1 class="title text-xl text-primary-500"> | ||||
|           <%= invite.name %> | ||||
|         </h1> | ||||
|  | ||||
|         <%= if invite.disabled_at |> is_nil() do %> | ||||
|           <h2 class="title text-md text-primary-500"> | ||||
|             Uses Left: <%= invite.uses_left || "Unlimited" %> | ||||
|           </h2> | ||||
|         <% else %> | ||||
|           <h2 class="title text-md text-primary-500"> | ||||
|             Invite Disabled | ||||
|           </h2> | ||||
|         <% end %> | ||||
|  | ||||
|         <code class="text-xs px-4 py-2 rounded-lg text-gray-100 bg-primary-800"> | ||||
|           <%= Routes.user_registration_url(@socket, :new, invite: invite.token) %> | ||||
|         </code> | ||||
|  | ||||
|         <div class="flex space-x-4 justify-center items-center"> | ||||
|           <%= live_patch "Edit", to: Routes.invite_index_path(@socket, :edit, invite), | ||||
|             class: "text-primary-500 link" %> | ||||
|  | ||||
|           <%= link "Delete", to: "#", | ||||
|             class: "text-primary-500 link", | ||||
|             phx_click: "delete", | ||||
|             phx_value_id: invite.id, | ||||
|             data: [confirm: "Are you sure?"] %> | ||||
|  | ||||
|           <%= if invite.disabled_at |> is_nil() do %> | ||||
|             <a href="#" class="text-primary-500 link" | ||||
|               phx-click="disable" phx-value-id="<%= invite.id %>"> | ||||
|               Disable | ||||
|             </a> | ||||
|           <% else %> | ||||
|             <a href="#" class="text-primary-500 link" | ||||
|               phx-click="enable" phx-value-id="<%= invite.id %>"> | ||||
|               Enable | ||||
|             </a> | ||||
|           <% end %> | ||||
|  | ||||
|           <%= if invite.disabled_at |> is_nil() and not(invite.uses_left |> is_nil()) do %> | ||||
|             <a href="#" class="text-primary-500 link" | ||||
|               phx-click="set_unlimited" phx-value-id="<%= invite.id %>" | ||||
|               data-confirm="Are you sure you want to make this invite unlimited?"> | ||||
|               Set Unlimited | ||||
|             </a> | ||||
|           <% end %> | ||||
|         </div> | ||||
|       </div> | ||||
|     <% end %> | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| <%= if @live_action in [:new, :edit] do %> | ||||
|   <%= live_modal CanneryWeb.InviteLive.FormComponent, | ||||
| @@ -6,33 +84,6 @@ | ||||
|     title: @page_title, | ||||
|     action: @live_action, | ||||
|     invite: @invite, | ||||
|     return_to: Routes.invite_index_path(@socket, :index) %> | ||||
|     return_to: Routes.invite_index_path(@socket, :index), | ||||
|     current_user: @current_user %> | ||||
| <% end %> | ||||
|  | ||||
| <table> | ||||
|   <thead> | ||||
|     <tr> | ||||
|       <th>Name</th> | ||||
|       <th>Token</th> | ||||
|       <th>Uses left</th> | ||||
|       <th></th> | ||||
|     </tr> | ||||
|   </thead> | ||||
|   <tbody id="invites"> | ||||
|     <%= for invite <- @invites do %> | ||||
|       <tr id="invite-<%= invite.id %>"> | ||||
|         <td><%= invite.name %></td> | ||||
|         <td><%= invite.token %></td> | ||||
|         <td><%= invite.uses_left || "Unlimited" %></td> | ||||
|  | ||||
|         <td> | ||||
|           <span><%= live_redirect "Show", to: Routes.invite_show_path(@socket, :show, invite) %></span> | ||||
|           <span><%= live_patch "Edit", to: Routes.invite_index_path(@socket, :edit, invite) %></span> | ||||
|           <span><%= link "Delete", to: "#", phx_click: "delete", phx_value_id: invite.id, data: [confirm: "Are you sure?"] %></span> | ||||
|         </td> | ||||
|       </tr> | ||||
|     <% end %> | ||||
|   </tbody> | ||||
| </table> | ||||
|  | ||||
| <span><%= live_patch "New Invite", to: Routes.invite_index_path(@socket, :new) %></span> | ||||
|   | ||||
| @@ -1,21 +0,0 @@ | ||||
| defmodule CanneryWeb.InviteLive.Show do | ||||
|   use CanneryWeb, :live_view | ||||
|  | ||||
|   alias Cannery.Invites | ||||
|  | ||||
|   @impl true | ||||
|   def mount(_params, session, socket) do | ||||
|     {:ok, socket |> assign_defaults(session)} | ||||
|   end | ||||
|  | ||||
|   @impl true | ||||
|   def handle_params(%{"id" => id}, _, socket) do | ||||
|     {:noreply, | ||||
|      socket | ||||
|      |> assign(:page_title, page_title(socket.assigns.live_action)) | ||||
|      |> assign(:invite, Invites.get_invite!(id))} | ||||
|   end | ||||
|  | ||||
|   defp page_title(:show), do: "Show Invite" | ||||
|   defp page_title(:edit), do: "Edit Invite" | ||||
| end | ||||
| @@ -1,27 +0,0 @@ | ||||
| <h1>Show Invite</h1> | ||||
|  | ||||
| <%= if @live_action in [:edit] do %> | ||||
|   <%= live_modal CanneryWeb.InviteLive.FormComponent, | ||||
|     id: @invite.id, | ||||
|     title: @page_title, | ||||
|     action: @live_action, | ||||
|     invite: @invite, | ||||
|     return_to: Routes.invite_show_path(@socket, :show, @invite) %> | ||||
| <% end %> | ||||
|  | ||||
| <ul> | ||||
|  | ||||
|   <li> | ||||
|     <strong>Name:</strong> | ||||
|     <%= @invite.name %> | ||||
|   </li> | ||||
|  | ||||
|   <li> | ||||
|     <strong>Token:</strong> | ||||
|     <%= @invite.token %> | ||||
|   </li> | ||||
|  | ||||
| </ul> | ||||
|  | ||||
| <span><%= live_patch "Edit", to: Routes.invite_show_path(@socket, :edit, @invite), class: "button" %></span> | ||||
| <span><%= live_redirect "Back", to: Routes.invite_index_path(@socket, :index) %></span> | ||||
| @@ -87,9 +87,6 @@ defmodule CanneryWeb.Router do | ||||
|     live "/invites", InviteLive.Index, :index | ||||
|     live "/invites/new", InviteLive.Index, :new | ||||
|     live "/invites/:id/edit", InviteLive.Index, :edit | ||||
|  | ||||
|     live "/invites/:id", InviteLive.Show, :show | ||||
|     live "/invites/:id/show/edit", InviteLive.Show, :edit | ||||
|   end | ||||
|  | ||||
|   scope "/", CanneryWeb do | ||||
|   | ||||
| @@ -11,6 +11,10 @@ | ||||
|       </div> | ||||
|     <% end %> | ||||
|  | ||||
|     <%= if @invite do %> | ||||
|       <%= hidden_input f, :invite_token, value: @invite.token %> | ||||
|     <% end %> | ||||
|  | ||||
|     <div class="grid grid-cols-3 justify-center items-center text-center space-x-4"> | ||||
|       <%= label f, :email, class: "title text-lg text-primary-500" %> | ||||
|       <%= email_input f, :email, required: true, class: "input input-primary col-span-2" %> | ||||
| @@ -24,7 +28,7 @@ | ||||
|     <%= error_tag f, :password %> | ||||
|  | ||||
|     <%= submit "Register", class: "btn btn-primary" %> | ||||
|      | ||||
|  | ||||
|     <hr class="hr"> | ||||
|  | ||||
|     <div class="flex flex-row justify-center items-center space-x-4"> | ||||
| @@ -34,4 +38,4 @@ | ||||
|         class: "btn btn-primary" %> | ||||
|     </div> | ||||
|   <% end %> | ||||
| </div> | ||||
| </div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user