rename to memex
This commit is contained in:
		
							
								
								
									
										18
									
								
								lib/memex_web/live/home_live.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								lib/memex_web/live/home_live.ex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| defmodule MemexWeb.HomeLive do | ||||
|   @moduledoc """ | ||||
|   Liveview for the home page | ||||
|   """ | ||||
|  | ||||
|   use MemexWeb, :live_view | ||||
|   alias Memex.Accounts | ||||
|   alias MemexWeb.Endpoint | ||||
|  | ||||
|   @version Mix.Project.config()[:version] | ||||
|  | ||||
|   @impl true | ||||
|   def mount(_params, _session, socket) do | ||||
|     admins = Accounts.list_users_by_role(:admin) | ||||
|     socket = socket |> assign(page_title: gettext("Home"), admins: admins, version: @version) | ||||
|     {:ok, socket} | ||||
|   end | ||||
| end | ||||
							
								
								
									
										135
									
								
								lib/memex_web/live/home_live.html.heex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								lib/memex_web/live/home_live.html.heex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | ||||
| <div class="mx-auto px-8 sm:px-16 flex flex-col justify-center items-center text-center space-y-4 max-w-3xl"> | ||||
|   <h1 class="title text-primary-600 text-2xl"> | ||||
|     <%= gettext("Welcome to Memex") %> | ||||
|   </h1> | ||||
|  | ||||
|   <h2 class="title text-primary-600 text-lg"> | ||||
|     <%= gettext("Shop from your local community") %> | ||||
|   </h2> | ||||
|  | ||||
|   <hr class="hr" /> | ||||
|  | ||||
|   <ul class="flex flex-col space-y-4 text-center"> | ||||
|     <li class="flex flex-col justify-center items-center | ||||
|       space-y-2"> | ||||
|       <b class="whitespace-nowrap"> | ||||
|         <%= gettext("Easy to Use:") %> | ||||
|       </b> | ||||
|       <p> | ||||
|         <%= gettext("Memex lets you easily shop from your local community") %> | ||||
|       </p> | ||||
|     </li> | ||||
|     <li class="flex flex-col justify-center items-center space-y-2"> | ||||
|       <b class="whitespace-nowrap"> | ||||
|         <%= gettext("Secure:") %> | ||||
|       </b> | ||||
|       <p> | ||||
|         <%= gettext("Self-host your own instance, or use an instance from someone you trust.") %> | ||||
|         <%= gettext("Your data stays with you, period") %> | ||||
|       </p> | ||||
|     </li> | ||||
|     <li class="flex flex-col justify-center items-center | ||||
|       space-y-2"> | ||||
|       <b class="whitespace-nowrap"> | ||||
|         <%= gettext("Simple:") %> | ||||
|       </b> | ||||
|       <p> | ||||
|         <%= gettext("Access from any internet-capable device") %> | ||||
|       </p> | ||||
|     </li> | ||||
|   </ul> | ||||
|  | ||||
|   <hr class="hr" /> | ||||
|  | ||||
|   <ul class="flex flex-col space-y-2 text-center justify-center"> | ||||
|     <h2 class="title text-primary-600 text-lg"> | ||||
|       <%= gettext("Instance Information") %> | ||||
|     </h2> | ||||
|  | ||||
|     <li class="flex flex-col justify-center space-x-2"> | ||||
|       <b> | ||||
|         <%= gettext("Admins:") %> | ||||
|       </b> | ||||
|       <p> | ||||
|         <%= if @admins |> Enum.empty?() do %> | ||||
|           <.link href={Routes.user_registration_path(Endpoint, :new)} class="hover:underline"> | ||||
|             <%= dgettext("prompts", "Register to setup Memex") %> | ||||
|           </.link> | ||||
|         <% else %> | ||||
|           <div class="flex flex-wrap justify-center space-x-2"> | ||||
|             <a :for={%{email: email} <- @admins} class="hover:underline" href={"mailto:#{email}"}> | ||||
|               <%= email %> | ||||
|             </a> | ||||
|           </div> | ||||
|         <% end %> | ||||
|       </p> | ||||
|     </li> | ||||
|  | ||||
|     <li class="flex flex-row justify-center space-x-2"> | ||||
|       <b><%= gettext("Registration:") %></b> | ||||
|       <p> | ||||
|         <%= case Application.get_env(:memex, Memex.Accounts)[:registration] do | ||||
|           "public" -> gettext("Public Signups") | ||||
|           _ -> gettext("Invite Only") | ||||
|         end %> | ||||
|       </p> | ||||
|     </li> | ||||
|  | ||||
|     <li class="flex flex-row justify-center items-center space-x-2"> | ||||
|       <b><%= gettext("Version:") %></b> | ||||
|       <.link | ||||
|         href="https://gitea.bubbletea.dev/shibao/memex/src/branch/stable/CHANGELOG.md" | ||||
|         class="flex flex-row justify-center items-center space-x-2 hover:underline" | ||||
|         target="_blank" | ||||
|         rel="noopener noreferrer" | ||||
|       > | ||||
|         <p> | ||||
|           <%= @version %> | ||||
|         </p> | ||||
|         <i class="fas fa-md fa-info-circle"></i> | ||||
|       </.link> | ||||
|     </li> | ||||
|   </ul> | ||||
|  | ||||
|   <hr class="hr" /> | ||||
|  | ||||
|   <ul class="flex flex-col space-y-2 text-center justify-center"> | ||||
|     <h2 class="title text-primary-600 text-lg"> | ||||
|       <%= gettext("Get involved!") %> | ||||
|     </h2> | ||||
|  | ||||
|     <li class="flex flex-col justify-center space-x-2"> | ||||
|       <.link | ||||
|         class="flex flex-row justify-center items-center space-x-2 hover:underline" | ||||
|         href="https://gitea.bubbletea.dev/shibao/memex" | ||||
|         target="_blank" | ||||
|         rel="noopener noreferrer" | ||||
|       > | ||||
|         <p><%= gettext("View the source code") %></p> | ||||
|         <i class="fas fa-md fa-code"></i> | ||||
|       </.link> | ||||
|     </li> | ||||
|     <li class="flex flex-col justify-center space-x-2"> | ||||
|       <.link | ||||
|         class="flex flex-row justify-center items-center space-x-2 hover:underline" | ||||
|         href="https://weblate.bubbletea.dev/engage/memex" | ||||
|         target="_blank" | ||||
|         rel="noopener noreferrer" | ||||
|       > | ||||
|         <p><%= gettext("Help translate") %></p> | ||||
|         <i class="fas fa-md fa-language"></i> | ||||
|       </.link> | ||||
|     </li> | ||||
|     <li class="flex flex-col justify-center space-x-2"> | ||||
|       <.link | ||||
|         class="flex flex-row justify-center items-center space-x-2 hover:underline" | ||||
|         href="https://gitea.bubbletea.dev/shibao/memex/issues/new" | ||||
|         target="_blank" | ||||
|         rel="noopener noreferrer" | ||||
|       > | ||||
|         <p><%= gettext("Report bugs or request features") %></p> | ||||
|         <i class="fas fa-md fa-spider"></i> | ||||
|       </.link> | ||||
|     </li> | ||||
|   </ul> | ||||
| </div> | ||||
							
								
								
									
										19
									
								
								lib/memex_web/live/init_assigns.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								lib/memex_web/live/init_assigns.ex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| defmodule MemexWeb.InitAssigns do | ||||
|   @moduledoc """ | ||||
|   Ensures common `assigns` are applied to all LiveViews attaching this hook. | ||||
|   """ | ||||
|   import Phoenix.Component | ||||
|   alias Memex.Accounts | ||||
|  | ||||
|   def on_mount(:default, _params, %{"locale" => locale, "user_token" => user_token}, socket) do | ||||
|     Gettext.put_locale(locale) | ||||
|  | ||||
|     socket = | ||||
|       socket | ||||
|       |> assign_new(:current_user, fn -> Accounts.get_user_by_session_token(user_token) end) | ||||
|  | ||||
|     {:cont, socket} | ||||
|   end | ||||
|  | ||||
|   def on_mount(:default, _params, _session, socket), do: {:cont, socket} | ||||
| end | ||||
							
								
								
									
										89
									
								
								lib/memex_web/live/invite_live/form_component.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								lib/memex_web/live/invite_live/form_component.ex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| defmodule MemexWeb.InviteLive.FormComponent do | ||||
|   @moduledoc """ | ||||
|   Livecomponent that can update or create an Memex.Accounts.Invite | ||||
|   """ | ||||
|  | ||||
|   use MemexWeb, :live_component | ||||
|   alias Ecto.Changeset | ||||
|   alias Memex.Accounts.{Invite, Invites, User} | ||||
|   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(%{})} | ||||
|   end | ||||
|  | ||||
|   @impl true | ||||
|   def handle_event("validate", %{"invite" => invite_params}, socket) do | ||||
|     {:noreply, socket |> assign_changeset(invite_params)} | ||||
|   end | ||||
|  | ||||
|   def handle_event("save", %{"invite" => invite_params}, %{assigns: %{action: action}} = socket) do | ||||
|     save_invite(socket, action, invite_params) | ||||
|   end | ||||
|  | ||||
|   defp assign_changeset( | ||||
|          %{assigns: %{action: action, current_user: user, invite: invite}} = socket, | ||||
|          invite_params | ||||
|        ) do | ||||
|     changeset_action = | ||||
|       case action do | ||||
|         :new -> :insert | ||||
|         :edit -> :update | ||||
|       end | ||||
|  | ||||
|     changeset = | ||||
|       case action do | ||||
|         :new -> Invite.create_changeset(user, "example_token", invite_params) | ||||
|         :edit -> invite |> Invite.update_changeset(invite_params) | ||||
|       end | ||||
|  | ||||
|     changeset = | ||||
|       case changeset |> Changeset.apply_action(changeset_action) do | ||||
|         {:ok, _data} -> changeset | ||||
|         {:error, changeset} -> changeset | ||||
|       end | ||||
|  | ||||
|     socket |> assign(:changeset, changeset) | ||||
|   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_navigate(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_navigate(to: return_to) | ||||
|  | ||||
|         {:error, %Changeset{} = changeset} -> | ||||
|           socket |> assign(changeset: changeset) | ||||
|       end | ||||
|  | ||||
|     {:noreply, socket} | ||||
|   end | ||||
| end | ||||
							
								
								
									
										37
									
								
								lib/memex_web/live/invite_live/form_component.html.heex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								lib/memex_web/live/invite_live/form_component.html.heex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| <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" | ||||
|   > | ||||
|     <div | ||||
|       :if={@changeset.action && not @changeset.valid?()} | ||||
|       class="invalid-feedback col-span-3 text-center" | ||||
|     > | ||||
|       <%= changeset_errors(@changeset) %> | ||||
|     </div> | ||||
|  | ||||
|     <%= 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") %> | ||||
|     <span class="col-span-3 text-primary-400 italic text-center"> | ||||
|       <%= gettext(~s/Leave "Uses left" blank to make invite unlimited/) %> | ||||
|     </span> | ||||
|  | ||||
|     <%= submit(dgettext("actions", "Save"), | ||||
|       class: "mx-auto btn btn-primary col-span-3", | ||||
|       phx_disable_with: dgettext("prompts", "Saving...") | ||||
|     ) %> | ||||
|   </.form> | ||||
| </div> | ||||
							
								
								
									
										157
									
								
								lib/memex_web/live/invite_live/index.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								lib/memex_web/live/invite_live/index.ex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,157 @@ | ||||
| defmodule MemexWeb.InviteLive.Index do | ||||
|   @moduledoc """ | ||||
|   Liveview to show a Memex.Accounts.Invite index | ||||
|   """ | ||||
|  | ||||
|   use MemexWeb, :live_view | ||||
|   import MemexWeb.Components.{InviteCard, UserCard} | ||||
|   alias Memex.Accounts | ||||
|   alias Memex.Accounts.{Invite, Invites} | ||||
|   alias MemexWeb.{Endpoint, HomeLive} | ||||
|   alias Phoenix.LiveView.JS | ||||
|  | ||||
|   @impl true | ||||
|   def mount(_params, _session, %{assigns: %{current_user: current_user}} = socket) do | ||||
|     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, HomeLive) | ||||
|         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", "%{invite_name} deleted succesfully", invite_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", "%{invite_name} updated succesfully", invite_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", "%{invite_name} enabled succesfully", invite_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", "%{invite_name} disabled succesfully", invite_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", _params, 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", "%{user_email} deleted succesfully", user_email: 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 | ||||
							
								
								
									
										149
									
								
								lib/memex_web/live/invite_live/index.html.heex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								lib/memex_web/live/invite_live/index.html.heex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | ||||
| <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> | ||||
|  | ||||
|     <.link patch={Routes.invite_index_path(Endpoint, :new)} class="btn btn-primary"> | ||||
|       <%= dgettext("actions", "Invite someone new!") %> | ||||
|     </.link> | ||||
|   <% else %> | ||||
|     <.link patch={Routes.invite_index_path(Endpoint, :new)} class="btn btn-primary"> | ||||
|       <%= dgettext("actions", "Create Invite") %> | ||||
|     </.link> | ||||
|   <% end %> | ||||
|  | ||||
|   <div class="w-full flex flex-row flex-wrap justify-center items-stretch"> | ||||
|     <.invite_card :for={invite <- @invites} invite={invite} current_user={@current_user}> | ||||
|       <:code_actions> | ||||
|         <form phx-submit="copy_to_clipboard"> | ||||
|           <button | ||||
|             type="submit" | ||||
|             class="mx-2 my-1 btn btn-primary" | ||||
|             phx-click={JS.dispatch("memex:clipcopy", to: "#code-#{invite.id}")} | ||||
|           > | ||||
|             <%= dgettext("actions", "Copy to clipboard") %> | ||||
|           </button> | ||||
|         </form> | ||||
|       </:code_actions> | ||||
|       <.link | ||||
|         patch={Routes.invite_index_path(Endpoint, :edit, invite)} | ||||
|         class="text-primary-600 link" | ||||
|         data-qa={"edit-#{invite.id}"} | ||||
|       > | ||||
|         <i class="fa-fw fa-lg fas fa-edit"></i> | ||||
|       </.link> | ||||
|  | ||||
|       <.link | ||||
|         href="#" | ||||
|         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 %{invite_name}?", | ||||
|             invite_name: invite.name | ||||
|           ) | ||||
|         } | ||||
|         data-qa={"delete-#{invite.id}"} | ||||
|       > | ||||
|         <i class="fa-fw fa-lg fas fa-trash"></i> | ||||
|       </.link> | ||||
|  | ||||
|       <a | ||||
|         href="#" | ||||
|         class="btn btn-primary" | ||||
|         phx-click={if invite.disabled_at, do: "enable_invite", else: "disable_invite"} | ||||
|         phx-value-id={invite.id} | ||||
|       > | ||||
|         <%= if invite.disabled_at, do: gettext("Enable"), else: gettext("Disable") %> | ||||
|       </a> | ||||
|  | ||||
|       <a | ||||
|         :if={invite.disabled_at |> is_nil() and not (invite.uses_left |> is_nil())} | ||||
|         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 %{invite_name} unlimited?", | ||||
|             invite_name: invite.name | ||||
|           ) | ||||
|         } | ||||
|       > | ||||
|         <%= gettext("Set Unlimited") %> | ||||
|       </a> | ||||
|     </.invite_card> | ||||
|   </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-stretch"> | ||||
|       <.user_card :for={admin <- @admins} user={admin}> | ||||
|         <.link | ||||
|           href="#" | ||||
|           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 | ||||
|             ) | ||||
|           } | ||||
|         > | ||||
|           <i class="fa-fw fa-lg fas fa-trash"></i> | ||||
|         </.link> | ||||
|       </.user_card> | ||||
|     </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-stretch"> | ||||
|       <.user_card :for={user <- @users} user={user}> | ||||
|         <.link | ||||
|           href="#" | ||||
|           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 | ||||
|             ) | ||||
|           } | ||||
|         > | ||||
|           <i class="fa-fw fa-lg fas fa-trash"></i> | ||||
|         </.link> | ||||
|       </.user_card> | ||||
|     </div> | ||||
|   <% end %> | ||||
| </div> | ||||
|  | ||||
| <.modal :if={@live_action in [:new, :edit]} return_to={Routes.invite_index_path(Endpoint, :index)}> | ||||
|   <.live_component | ||||
|     module={MemexWeb.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> | ||||
							
								
								
									
										81
									
								
								lib/memex_web/live/live_helpers.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								lib/memex_web/live/live_helpers.ex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| defmodule MemexWeb.LiveHelpers do | ||||
|   @moduledoc """ | ||||
|   Contains common helper functions for liveviews | ||||
|   """ | ||||
|  | ||||
|   import Phoenix.Component | ||||
|   alias Phoenix.LiveView.JS | ||||
|  | ||||
|   @doc """ | ||||
|   Renders a live component inside a modal. | ||||
|  | ||||
|   The rendered modal receives a `:return_to` option to properly update | ||||
|   the URL when the modal is closed. | ||||
|  | ||||
|   ## Examples | ||||
|  | ||||
|       <.modal return_to={Routes.<%= schema.singular %>_index_path(Endpoint, :index)}> | ||||
|         <.live_component | ||||
|           module={<%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.FormComponent} | ||||
|           id={@<%= schema.singular %>.id || :new} | ||||
|           title={@page_title} | ||||
|           action={@live_action} | ||||
|           return_to={Routes.<%= schema.singular %>_index_path(Endpoint, :index)} | ||||
|           <%= schema.singular %>: @<%= schema.singular %> | ||||
|         /> | ||||
|       </.modal> | ||||
|   """ | ||||
|   def modal(assigns) do | ||||
|     ~H""" | ||||
|     <.link | ||||
|       id="modal-bg" | ||||
|       patch={@return_to} | ||||
|       class="fade-in fixed z-10 left-0 top-0 | ||||
|         w-full h-full overflow-hidden | ||||
|         p-8 flex flex-col justify-center items-center cursor-auto" | ||||
|       style="background-color: rgba(0,0,0,0.4);" | ||||
|       phx-remove={hide_modal()} | ||||
|     > | ||||
|       <span class="hidden"></span> | ||||
|     </.link> | ||||
|  | ||||
|     <div | ||||
|       id="modal" | ||||
|       class="fixed z-10 left-0 top-0 pointer-events-none | ||||
|         w-full h-full overflow-hidden | ||||
|         p-4 sm:p-8 flex flex-col justify-center items-center" | ||||
|     > | ||||
|       <div | ||||
|         id="modal-content" | ||||
|         class="fade-in-scale w-full max-w-3xl relative | ||||
|           pointer-events-auto overflow-hidden | ||||
|           px-8 py-4 sm:py-8 | ||||
|           flex flex-col justify-start items-center | ||||
|           bg-white border-2 rounded-lg" | ||||
|       > | ||||
|         <.link | ||||
|           id="close" | ||||
|           href={@return_to} | ||||
|           class="absolute top-8 right-10 | ||||
|             text-gray-500 hover:text-gray-800 | ||||
|             transition-all duration-500 ease-in-out" | ||||
|           phx-remove={hide_modal()} | ||||
|         > | ||||
|           <i class="fa-fw fa-lg fas fa-times"></i> | ||||
|         </.link> | ||||
|  | ||||
|         <div class="overflow-x-hidden overflow-y-auto w-full p-8 flex flex-col space-y-4 justify-start items-center"> | ||||
|           <%= render_slot(@inner_block) %> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     """ | ||||
|   end | ||||
|  | ||||
|   def hide_modal(js \\ %JS{}) do | ||||
|     js | ||||
|     |> JS.hide(to: "#modal", transition: "fade-out") | ||||
|     |> JS.hide(to: "#modal-bg", transition: "fade-out") | ||||
|     |> JS.hide(to: "#modal-content", transition: "fade-out-scale") | ||||
|   end | ||||
| end | ||||
		Reference in New Issue
	
	Block a user