rename to cannery

This commit is contained in:
2023-02-25 15:47:37 -05:00
parent bc034c0361
commit a778f5a61f
128 changed files with 999 additions and 998 deletions

View File

@ -0,0 +1,70 @@
defmodule CanneryWeb.Components.InviteCard do
@moduledoc """
Display card for an invite
"""
use CanneryWeb, :component
alias Cannery.Accounts.{Invite, Invites, User}
alias CanneryWeb.Endpoint
attr :invite, Invite, required: true
attr :current_user, User, required: true
slot(:inner_block)
slot(:code_actions)
def invite_card(%{invite: invite, current_user: current_user} = assigns) do
assigns =
assigns
|> assign(:use_count, Invites.get_use_count(invite, current_user))
|> assign_new(:code_actions, fn -> [] end)
~H"""
<div class="mx-4 my-2 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
transition-all duration-300 ease-in-out">
<h1 class="title text-xl">
<%= @invite.name %>
</h1>
<%= if @invite.disabled_at |> is_nil() do %>
<h2 class="title text-md">
<%= if @invite.uses_left do %>
<%= gettext(
"Uses Left: %{uses_left_count}",
uses_left_count: @invite.uses_left
) %>
<% else %>
<%= gettext("Uses Left: Unlimited") %>
<% end %>
</h2>
<% else %>
<h2 class="title text-md">
<%= gettext("Invite Disabled") %>
</h2>
<% end %>
<.qr_code
content={Routes.user_registration_url(Endpoint, :new, invite: @invite.token)}
filename={@invite.name}
/>
<h2 :if={@use_count != 0} class="title text-md">
<%= gettext("Uses: %{uses_count}", uses_count: @use_count) %>
</h2>
<div class="flex flex-row flex-wrap justify-center items-center">
<code
id={"code-#{@invite.id}"}
class="mx-2 my-1 text-xs px-4 py-2 rounded-lg text-center break-all text-gray-100 bg-primary-800"
phx-no-format
><%= Routes.user_registration_url(Endpoint, :new, invite: @invite.token) %></code>
<%= render_slot(@code_actions) %>
</div>
<div :if={@inner_block} class="flex space-x-4 justify-center items-center">
<%= render_slot(@inner_block) %>
</div>
</div>
"""
end
end

View File

@ -0,0 +1,87 @@
defmodule CanneryWeb.Components.TableComponent do
@moduledoc """
Livecomponent that presents a resortable table
It takes the following required assigns:
- `:columns`: An array of maps containing the following keys
- `:label`: A gettext'd or otherwise user-facing string label for the
column. Can be nil
- `:key`: A string key used for sorting
- `:class`: Extra classes to be applied to the column element, if desired.
Optional
- `:sortable`: If false, will prevent the user from sorting with it.
Optional
- `:values`: An array of maps containing data for each row. Each map is
string-keyed with the associated column key to the following values:
- A single element, like string, integer or Phoenix.LiveView.Rendered
object, like returned from the ~H sigil
- A tuple, containing a custom value used for sorting, and the displayed
content.
"""
use CanneryWeb, :live_component
alias Phoenix.LiveView.Socket
@impl true
@spec update(
%{
required(:columns) =>
list(%{
required(:label) => String.t() | nil,
required(:key) => String.t() | nil,
optional(:class) => String.t(),
optional(:sortable) => false
}),
required(:rows) =>
list(%{
(key :: String.t()) => any() | {custom_sort_value :: String.t(), value :: any()}
}),
optional(any()) => any()
},
Socket.t()
) :: {:ok, Socket.t()}
def update(%{columns: columns, rows: rows} = assigns, socket) do
initial_key = columns |> List.first() |> Map.get(:key)
rows = rows |> Enum.sort_by(fn row -> row |> Map.get(initial_key) end, :asc)
socket =
socket
|> assign(assigns)
|> assign(columns: columns, rows: rows, last_sort_key: initial_key, sort_mode: :asc)
{:ok, socket}
end
@impl true
def handle_event(
"sort_by",
%{"sort-key" => key},
%{assigns: %{rows: rows, last_sort_key: key, sort_mode: sort_mode}} = socket
) do
sort_mode = if sort_mode == :asc, do: :desc, else: :asc
rows = rows |> sort_by_custom_sort_value_or_value(key, sort_mode)
{:noreply, socket |> assign(sort_mode: sort_mode, rows: rows)}
end
def handle_event(
"sort_by",
%{"sort-key" => key},
%{assigns: %{rows: rows}} = socket
) do
rows = rows |> sort_by_custom_sort_value_or_value(key, :asc)
{:noreply, socket |> assign(last_sort_key: key, sort_mode: :asc, rows: rows)}
end
defp sort_by_custom_sort_value_or_value(rows, key, sort_mode) do
rows
|> Enum.sort_by(
fn row ->
case row |> Map.get(key) do
{custom_sort_key, _value} -> custom_sort_key
value -> value
end
end,
sort_mode
)
end
end

View File

@ -0,0 +1,48 @@
<div class="w-full overflow-x-auto border border-gray-600 rounded-lg shadow-lg bg-black">
<table class="min-w-full table-auto text-center bg-white">
<thead class="border-b border-primary-600">
<tr>
<%= for %{key: key, label: label} = column <- @columns do %>
<%= if column |> Map.get(:sortable, true) do %>
<th class={["p-2", column[:class]]}>
<span
class="cursor-pointer"
phx-click="sort_by"
phx-value-sort-key={key}
phx-target={@myself}
>
<span class="underline"><%= label %></span>
<%= if @last_sort_key == key do %>
<%= case @sort_mode do %>
<% :asc -> %>
<i class="fas fa-sm fa-chevron-down"></i>
<% :desc -> %>
<i class="fas fa-sm fa-chevron-up"></i>
<% end %>
<% else %>
<i class="fas fa-sm fa-chevron-up opacity-0"></i>
<% end %>
</span>
</th>
<% else %>
<th class={["p-2", column[:class]]}>
<%= label %>
</th>
<% end %>
<% end %>
</tr>
</thead>
<tbody>
<tr :for={values <- @rows}>
<td :for={%{key: key} = value <- @columns} class={["p-2", value[:class]]}>
<%= case values |> Map.get(key) do %>
<% {_custom_sort_value, value} -> %>
<%= value %>
<% value -> %>
<%= value %>
<% end %>
</td>
</tr>
</tbody>
</table>
</div>

View File

@ -0,0 +1,100 @@
defmodule CanneryWeb.Components.Topbar do
@moduledoc """
Component that renders a topbar with user functions/links
"""
use CanneryWeb, :component
alias Cannery.Accounts
alias CanneryWeb.{Endpoint, HomeLive}
def topbar(assigns) do
assigns =
%{results: [], title_content: nil, flash: nil, current_user: nil} |> Map.merge(assigns)
~H"""
<nav role="navigation" class="mb-8 px-8 py-4 w-full bg-primary-400">
<div class="flex flex-col sm:flex-row justify-between items-center">
<div class="mb-4 sm:mb-0 sm:mr-8 flex flex-row justify-start items-center space-x-2">
<.link
navigate={Routes.live_path(Endpoint, HomeLive)}
class="mx-2 my-1 leading-5 text-xl text-white hover:underline"
>
<%= gettext("Cannery") %>
</.link>
<%= if @title_content do %>
<span class="mx-2 my-1">
|
</span>
<%= @title_content %>
<% end %>
</div>
<hr class="mb-2 sm:hidden hr-light" />
<ul class="flex flex-row flex-wrap justify-center items-center
text-lg text-white text-ellipsis">
<%= if @current_user do %>
<li :if={@current_user.role == :admin} class="mx-2 my-1">
<.link
navigate={Routes.invite_index_path(Endpoint, :index)}
class="text-white text-white hover:underline"
>
<%= gettext("Invites") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
navigate={Routes.user_settings_path(Endpoint, :edit)}
class="text-white text-white hover:underline truncate"
>
<%= @current_user.email %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
href={Routes.user_session_path(Endpoint, :delete)}
method="delete"
data-confirm={dgettext("prompts", "Are you sure you want to log out?")}
>
<i class="fas fa-sign-out-alt"></i>
</.link>
</li>
<li
:if={
@current_user.role == :admin and function_exported?(Routes, :live_dashboard_path, 2)
}
class="mx-2 my-1"
>
<.link
navigate={Routes.live_dashboard_path(Endpoint, :home)}
class="text-white text-white hover:underline"
>
<i class="fas fa-gauge"></i>
</.link>
</li>
<% else %>
<li :if={Accounts.allow_registration?()} class="mx-2 my-1">
<.link
navigate={Routes.user_registration_path(Endpoint, :new)}
class="text-white text-white hover:underline truncate"
>
<%= dgettext("actions", "Register") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
navigate={Routes.user_session_path(Endpoint, :new)}
class="text-white text-white hover:underline truncate"
>
<%= dgettext("actions", "Log in") %>
</.link>
</li>
<% end %>
</ul>
</div>
</nav>
"""
end
end

View File

@ -0,0 +1,48 @@
defmodule CanneryWeb.Components.UserCard do
@moduledoc """
Display card for a user
"""
use CanneryWeb, :component
def user_card(assigns) do
~H"""
<div
id={"user-#{@user.id}"}
class="mx-4 my-2 px-8 py-4 flex flex-col justify-center items-center text-center
border border-gray-400 rounded-lg shadow-lg hover:shadow-md
transition-all duration-300 ease-in-out"
>
<h1 class="px-4 py-2 rounded-lg title text-xl break-all">
<%= @user.email %>
</h1>
<h3 class="px-4 py-2 rounded-lg title text-lg">
<p>
<%= if @user.confirmed_at do %>
<%= gettext(
"User was confirmed at%{confirmed_datetime}",
confirmed_datetime: ""
) %>
<.datetime datetime={@user.confirmed_at} />
<% else %>
<%= gettext("Email unconfirmed") %>
<% end %>
</p>
<p>
<%= gettext(
"User registered on%{registered_datetime}",
registered_datetime: ""
) %>
<.datetime datetime={@user.inserted_at} />
</p>
</h3>
<div :if={@inner_block} class="px-4 py-2 flex space-x-4 justify-center items-center">
<%= render_slot(@inner_block) %>
</div>
</div>
"""
end
end