refactor(portal): Use popover with UTC timestamp for datetime fields (#5712)

Fixes #5249 to allow copy-pasting the timestamp
Fixes #5635 by virtue of using a relative datetime there.
Fixes #5225 

<img width="579" alt="Screenshot 2024-07-03 at 10 58 11 PM"
src="https://github.com/firezone/firezone/assets/167144/261a5f58-ab9c-40b3-a26f-3adcff228aa9">
This commit is contained in:
Jamil
2024-07-04 09:37:33 -07:00
committed by GitHub
parent 1e7d3a40d2
commit 140a2979da
7 changed files with 71 additions and 31 deletions

View File

@@ -771,19 +771,53 @@ defmodule Web.CoreComponents do
assigns = assign_new(assigns, :relative_to, fn -> DateTime.utc_now() end)
~H"""
<span
:if={not is_nil(@datetime)}
class="underline underline-offset-2 decoration-dotted"
title={@datetime}
>
<%= Cldr.DateTime.Relative.to_string!(@datetime, Web.CLDR, relative_to: @relative_to) %>
</span>
<.popover :if={not is_nil(@datetime)}>
<:target>
<span class="underline underline-offset-2 decoration-dashed">
<%= Cldr.DateTime.Relative.to_string!(@datetime, Web.CLDR, relative_to: @relative_to)
|> String.capitalize() %>
</span>
</:target>
<:content>
<%= @datetime %>
</:content>
</.popover>
<span :if={is_nil(@datetime)}>
never
Never
</span>
"""
end
@doc """
Renders a popover element with title and content.
"""
slot :target, required: true
slot :content, required: true
def popover(assigns) do
# Any id will do
target_id = "popover-#{System.unique_integer([:positive, :monotonic])}"
assigns = assign(assigns, :target_id, target_id)
~H"""
<span data-popover-target={@target_id}>
<%= render_slot(@target) %>
</span>
<div data-popover id={@target_id} role="tooltip" class={~w[
absolute z-10 invisible inline-block
text-sm text-neutral-500 transition-opacity
duration-50 bg-white border border-neutral-200
rounded shadow-sm opacity-0
]}>
<div class="px-3 py-2">
<%= render_slot(@content) %>
</div>
<div data-popper-arrow></div>
</div>
"""
end
@doc """
Renders online or offline status using an `online?` field of the schema.
"""

View File

@@ -150,13 +150,7 @@ defmodule Web.Policies.Show do
Created
</:label>
<:value>
<.datetime datetime={@policy.inserted_at} /> by
<.link
navigate={~p"/#{@account}/actors/#{@policy.created_by_identity.actor.id}"}
class={link_style()}
>
<%= @policy.created_by_identity.actor.name %>
</.link>
<.created_by account={@account} schema={@policy} />
</:value>
</.vertical_table_row>
</.vertical_table>

View File

@@ -202,7 +202,7 @@ defmodule Web.ConnCase do
### Helpers to test formatted time units
def around_now?(string) do
if string =~ "now" do
if string =~ "Now" do
true
else
[_all, seconds] = Regex.run(~r/([0-9]+) second[s]? ago/, string)

View File

@@ -99,7 +99,7 @@ defmodule Web.Live.Actors.IndexTest do
assert row["groups"] =~ group.name
end
assert row["last signed in"] == "never"
assert row["last signed in"] == "Never"
end)
end
end
@@ -133,7 +133,7 @@ defmodule Web.Live.Actors.IndexTest do
|> render()
|> table_to_map()
|> with_table_row("name", actor.name, fn row ->
assert row["last signed in"] == "1 hour ago"
assert String.contains?(row["last signed in"], "1 hour ago")
end)
end
end

View File

@@ -359,7 +359,7 @@ defmodule Web.Live.Actors.ShowTest do
fn row ->
assert row["actions"] =~ "Delete"
assert row["created"] =~ "by #{actor.name}"
assert row["last signed in"] == "never"
assert row["last signed in"] == "Never"
end
)
|> with_table_row(
@@ -368,7 +368,7 @@ defmodule Web.Live.Actors.ShowTest do
fn row ->
refute row["actions"]
assert row["created"] =~ "by #{synced_identity.provider.name} sync"
assert row["last signed in"] == "never"
assert row["last signed in"] == "Never"
end
)
end
@@ -661,14 +661,20 @@ defmodule Web.Live.Actors.ShowTest do
|> table_to_map()
assert row1["type"] == "browser"
assert row1["expires"] in ["tomorrow", "in 24 hours"]
assert row1["last used"] == "never"
assert String.contains?(row1["expires"], "Tomorrow") ||
String.contains?(row1["expires"], "In 24 hours")
assert row1["last used"] == "Never"
assert around_now?(row1["created"])
assert row1["actions"] == "Revoke"
assert row2["type"] == "client"
assert row2["expires"] in ["tomorrow", "in 24 hours"]
assert row2["last used"] == "never"
assert String.contains?(row2["expires"], "Tomorrow") ||
String.contains?(row2["expires"], "In 24 hours")
assert row2["last used"] == "Never"
assert around_now?(row2["created"])
assert row2["actions"] == "Revoke"
end
@@ -928,7 +934,7 @@ defmodule Web.Live.Actors.ShowTest do
|> element("#actor")
|> render()
|> vertical_table_to_map() == %{
"last signed in" => "never",
"last signed in" => "Never",
"name" => actor.name,
"role" => "service account"
}
@@ -1084,14 +1090,20 @@ defmodule Web.Live.Actors.ShowTest do
|> table_to_map()
assert row1["type"] == "browser"
assert row1["expires"] in ["tomorrow", "in 24 hours"]
assert row1["last used"] == "never"
assert String.contains?(row1["expires"], "Tomorrow") ||
String.contains?(row1["expires"], "In 24 hours")
assert row1["last used"] == "Never"
assert around_now?(row1["created"])
assert row1["actions"] == "Revoke"
assert row2["type"] == "client"
assert row2["expires"] in ["tomorrow", "in 24 hours"]
assert row2["last used"] == "never"
assert String.contains?(row2["expires"], "Tomorrow") ||
String.contains?(row2["expires"], "In 24 hours")
assert row2["last used"] == "Never"
assert around_now?(row2["created"])
assert row2["actions"] == "Revoke"
end

View File

@@ -214,7 +214,7 @@ defmodule Web.Live.Settings.ApiClients.ShowTest do
assert row1["name"] == token.name
assert row1["expires at"]
assert row1["last used"] == "never"
assert row1["last used"] == "Never"
assert row1["actions"] == "Revoke"
end

View File

@@ -140,7 +140,7 @@ defmodule Web.Live.Settings.IdentityProviders.IndexTest do
|> render()
|> table_to_map()
|> with_table_row("name", provider.name, fn row ->
assert row["sync status"] == "Synced 2 identities and 1 group 1 hour ago"
assert String.contains?(row["sync status"], "Synced 2 identities and 1 group 1 hour ago")
end)
provider =