towerops/lib/towerops_web/controllers/user_session_html/new.html.heex
Graham McIntire b56cdf4e9f
feat: dynamic favicon reflects real-time system health status
Favicon changes to green/yellow/red based on device and alert status.
LiveView computes the status server-side and passes it to a minimal
JS hook that generates PNG favicons via canvas.
2026-03-05 13:47:54 -06:00

170 lines
6.4 KiB
Text

<Layouts.app flash={@flash} current_scope={@current_scope}>
<div class="mx-auto max-w-4xl">
<div class="overflow-hidden rounded-2xl bg-white shadow-xl dark:bg-gray-900 lg:grid lg:grid-cols-5">
<!-- Left: Brand panel -->
<div class="hidden lg:col-span-2 lg:flex lg:flex-col lg:justify-between bg-gradient-to-br from-blue-600 via-blue-700 to-indigo-800 p-8 text-white">
<div>
<div class="flex items-center gap-2">
<.icon name="hero-signal" class="size-8" />
<span class="text-xl font-bold">TowerOps</span>
</div>
<p class="mt-6 text-lg font-medium leading-relaxed text-blue-100">
{t("Monitor your entire network from a single dashboard.")}
</p>
</div>
<div class="mt-auto">
<div class="rounded-lg bg-white/10 p-4 backdrop-blur-sm">
<p class="text-sm italic text-blue-100">
{t(
"\"TowerOps cut our response time to network issues by 80%. We can see problems before customers even notice.\""
)}
</p>
<p class="mt-3 text-xs font-semibold text-blue-200">{t("— WISP Network Operator")}</p>
</div>
</div>
</div>
<!-- Right: Login form -->
<div class="col-span-3 px-6 py-10 sm:px-12">
<!-- Mobile brand mark -->
<div class="mb-6 flex items-center justify-center gap-2 lg:hidden">
<.icon name="hero-signal" class="size-7 text-blue-600 dark:text-blue-400" />
<span class="text-lg font-bold text-gray-900 dark:text-white">TowerOps</span>
</div>
<div class="text-center lg:text-left">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
{t_auth("Log in")}
</h1>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
<%= if @current_scope do %>
{t_auth("You need to reauthenticate to perform sensitive actions on your account.")}
<% else %>
{t_auth("Don't have an account?")}
<.link
navigate={~p"/users/register"}
class="font-semibold text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
>
{t_auth("Sign up")}
</.link>
<% end %>
</p>
</div>
<div
:if={local_mail_adapter?()}
class="mt-5 rounded-lg bg-blue-50 p-4 dark:bg-blue-950"
>
<div class="flex">
<.icon
name="hero-information-circle"
class="h-6 w-6 flex-shrink-0 text-blue-600 dark:text-blue-400"
/>
<div class="ml-3">
<p class="text-sm text-blue-700 dark:text-blue-200">
{t_auth("You are running the local mail adapter.")}
</p>
<p class="mt-1 text-sm text-blue-700 dark:text-blue-200">
{t_auth("To see sent emails, visit %{link}.",
link:
raw(
~s(<a href="/dev/mailbox" class="font-medium underline">#{t_auth("the mailbox page")}</a>)
)
)}
</p>
</div>
</div>
</div>
<div class="mt-8 space-y-6">
<!-- Password Login -->
<.form
:let={f}
for={@form}
as={:user}
id="login_form_password"
action={~p"/users/log-in"}
>
<.input
readonly={!!@current_scope}
field={f[:email]}
id="user_email_password"
type="email"
label={t_auth("Email")}
autocomplete="email"
required
phx-mounted={JS.focus()}
/>
<.input
field={f[:password]}
id="user_password"
type="password"
label={t_auth("Password")}
autocomplete="current-password"
required
/>
<div class="flex items-center justify-between">
<label class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
<input
type="checkbox"
name={@form[:remember_me].name}
value="true"
class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800"
/>
{t_auth("Remember me")}
</label>
<.link
navigate={~p"/users/reset-password"}
class="text-sm font-semibold text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
>
{t_auth("Forgot password?")}
</.link>
</div>
<div class="mt-4">
<.button class="w-full" variant="primary">
{t_auth("Log in")} <span aria-hidden="true">→</span>
</.button>
</div>
</.form>
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-200 dark:border-white/10"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="bg-white px-3 text-gray-500 dark:bg-gray-900 dark:text-gray-500">
{t("or")}
</span>
</div>
</div>
<!-- Magic Link -->
<.form :let={f} for={@form} as={:user} id="login_form_magic" action={~p"/users/log-in"}>
<.input
readonly={!!@current_scope}
field={f[:email]}
id="user_email_magic"
type="email"
label={t_auth("Email")}
autocomplete="email"
required
/>
<.button class="w-full" variant="secondary">
<.icon name="hero-envelope" class="size-4" />
{t_auth("Send me a login link instead")}
</.button>
</.form>
<p class="text-center text-sm text-gray-500 dark:text-gray-500">
<.link
href={~p"/users/confirm"}
class="text-blue-600 hover:text-blue-700 dark:text-blue-400 hover:underline"
>
{t_auth("Didn't receive confirmation instructions?")}
</.link>
</p>
</div>
</div>
</div>
</div>
</Layouts.app>