add user auth
This commit is contained in:
parent
fd82d6e7bd
commit
41eaec5c8e
12 changed files with 627 additions and 221 deletions
|
|
@ -119,6 +119,8 @@
|
||||||
color: oklch(58% 0.233 277.117) !important;
|
color: oklch(58% 0.233 277.117) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Input field dark mode fixes */
|
||||||
|
|
||||||
/* Marker cluster styles */
|
/* Marker cluster styles */
|
||||||
.marker-cluster-small {
|
.marker-cluster-small {
|
||||||
background-color: rgba(16, 185, 129, 0.8);
|
background-color: rgba(16, 185, 129, 0.8);
|
||||||
|
|
@ -326,4 +328,60 @@
|
||||||
|
|
||||||
.historical-dot-marker {
|
.historical-dot-marker {
|
||||||
z-index: 15 !important;
|
z-index: 15 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input field dark mode fixes - Override DaisyUI CSS variables for dark theme */
|
||||||
|
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--color-base-100: #2d3748 !important;
|
||||||
|
--color-base-200: #1a202c !important;
|
||||||
|
--color-base-300: #171923 !important;
|
||||||
|
--color-base-content: #f7fafc !important;
|
||||||
|
--input-bg: #2d3748;
|
||||||
|
--input-color: #f7fafc;
|
||||||
|
--input-border: #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ultra-specific selector to override DaisyUI */
|
||||||
|
html[data-theme="dark"] .card .input,
|
||||||
|
html[data-theme="dark"] .card .input-bordered,
|
||||||
|
html[data-theme="dark"] .card input.input,
|
||||||
|
html[data-theme="dark"] .card input.input-bordered,
|
||||||
|
html[data-theme="dark"] .card input[type="email"],
|
||||||
|
html[data-theme="dark"] .card input[type="password"],
|
||||||
|
html[data-theme="dark"] .form-control input.input,
|
||||||
|
html[data-theme="dark"] .form-control input.input-bordered,
|
||||||
|
html[data-theme="dark"] .form-control input[type="email"],
|
||||||
|
html[data-theme="dark"] .form-control input[type="password"] {
|
||||||
|
background-color: #2d3748 !important;
|
||||||
|
color: #f7fafc !important;
|
||||||
|
border-color: #4a5568 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .card .input:focus,
|
||||||
|
html[data-theme="dark"] .card .input-bordered:focus,
|
||||||
|
html[data-theme="dark"] .card input.input:focus,
|
||||||
|
html[data-theme="dark"] .card input.input-bordered:focus,
|
||||||
|
html[data-theme="dark"] .card input[type="email"]:focus,
|
||||||
|
html[data-theme="dark"] .card input[type="password"]:focus,
|
||||||
|
html[data-theme="dark"] .form-control input.input:focus,
|
||||||
|
html[data-theme="dark"] .form-control input.input-bordered:focus,
|
||||||
|
html[data-theme="dark"] .form-control input[type="email"]:focus,
|
||||||
|
html[data-theme="dark"] .form-control input[type="password"]:focus {
|
||||||
|
background-color: #2d3748 !important;
|
||||||
|
color: #f7fafc !important;
|
||||||
|
border-color: #6366f1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .input::placeholder,
|
||||||
|
html[data-theme="dark"] input.input::placeholder,
|
||||||
|
html[data-theme="dark"] input[type="email"]::placeholder,
|
||||||
|
html[data-theme="dark"] input[type="password"]::placeholder {
|
||||||
|
color: oklch(60% 0.029 256.847) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .checkbox,
|
||||||
|
html[data-theme="dark"] input.checkbox {
|
||||||
|
background-color: var(--input-bg) !important;
|
||||||
|
border-color: var(--input-border) !important;
|
||||||
}
|
}
|
||||||
|
|
@ -237,7 +237,7 @@ defmodule AprsmeWeb.CoreComponents do
|
||||||
def simple_form(assigns) do
|
def simple_form(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<.form :let={f} for={@for} as={@as} {@rest}>
|
<.form :let={f} for={@for} as={@as} {@rest}>
|
||||||
<div class="space-y-8 bg-white mt-10">
|
<div class="space-y-8 bg-base-100 mt-10">
|
||||||
{render_slot(@inner_block, f)}
|
{render_slot(@inner_block, f)}
|
||||||
<div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
|
<div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
|
||||||
{render_slot(action, f)}
|
{render_slot(action, f)}
|
||||||
|
|
@ -442,6 +442,7 @@ defmodule AprsmeWeb.CoreComponents do
|
||||||
"""
|
"""
|
||||||
attr :class, :string, default: ""
|
attr :class, :string, default: ""
|
||||||
attr :dev_mode, :boolean, default: false
|
attr :dev_mode, :boolean, default: false
|
||||||
|
attr :current_user, :any, default: nil
|
||||||
|
|
||||||
def header(assigns) do
|
def header(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
|
|
@ -455,7 +456,7 @@ defmodule AprsmeWeb.CoreComponents do
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-center hidden lg:flex">
|
<div class="navbar-center hidden lg:flex">
|
||||||
<.navigation variant={:horizontal} />
|
<.navigation variant={:horizontal} current_user={@current_user} />
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
<.theme_selector />
|
<.theme_selector />
|
||||||
|
|
@ -474,7 +475,7 @@ defmodule AprsmeWeb.CoreComponents do
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52"
|
class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52"
|
||||||
>
|
>
|
||||||
<.navigation variant={:vertical} />
|
<.navigation variant={:vertical} current_user={@current_user} />
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -750,6 +751,7 @@ defmodule AprsmeWeb.CoreComponents do
|
||||||
"""
|
"""
|
||||||
attr :class, :string, default: ""
|
attr :class, :string, default: ""
|
||||||
attr :variant, :atom, values: [:horizontal, :vertical], default: :horizontal
|
attr :variant, :atom, values: [:horizontal, :vertical], default: :horizontal
|
||||||
|
attr :current_user, :any, default: nil
|
||||||
|
|
||||||
def navigation(assigns) do
|
def navigation(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
|
|
@ -764,6 +766,29 @@ defmodule AprsmeWeb.CoreComponents do
|
||||||
{gettext("About")}
|
{gettext("About")}
|
||||||
</.link>
|
</.link>
|
||||||
</li>
|
</li>
|
||||||
|
<%= if @current_user do %>
|
||||||
|
<li>
|
||||||
|
<.link navigate="/users/settings" class="text-gray-900 hover:text-gray-700">
|
||||||
|
{gettext("Settings")}
|
||||||
|
</.link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<.link href="/users/log_out" method="delete" class="text-gray-900 hover:text-gray-700">
|
||||||
|
{gettext("Log out")}
|
||||||
|
</.link>
|
||||||
|
</li>
|
||||||
|
<% else %>
|
||||||
|
<li>
|
||||||
|
<.link navigate="/users/register" class="text-gray-900 hover:text-gray-700">
|
||||||
|
{gettext("Register")}
|
||||||
|
</.link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<.link navigate="/users/log_in" class="text-gray-900 hover:text-gray-700">
|
||||||
|
{gettext("Log in")}
|
||||||
|
</.link>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
<% else %>
|
<% else %>
|
||||||
<li><.link navigate="/" class="text-gray-900 hover:text-gray-700">{gettext("Home")}</.link></li>
|
<li><.link navigate="/" class="text-gray-900 hover:text-gray-700">{gettext("Home")}</.link></li>
|
||||||
|
|
@ -771,6 +796,29 @@ defmodule AprsmeWeb.CoreComponents do
|
||||||
<li>
|
<li>
|
||||||
<.link navigate="/about" class="text-gray-900 hover:text-gray-700">{gettext("About")}</.link>
|
<.link navigate="/about" class="text-gray-900 hover:text-gray-700">{gettext("About")}</.link>
|
||||||
</li>
|
</li>
|
||||||
|
<%= if @current_user do %>
|
||||||
|
<li>
|
||||||
|
<.link navigate="/users/settings" class="text-gray-900 hover:text-gray-700">
|
||||||
|
{gettext("Settings")}
|
||||||
|
</.link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<.link href="/users/log_out" method="delete" class="text-gray-900 hover:text-gray-700">
|
||||||
|
{gettext("Log out")}
|
||||||
|
</.link>
|
||||||
|
</li>
|
||||||
|
<% else %>
|
||||||
|
<li>
|
||||||
|
<.link navigate="/users/register" class="text-gray-900 hover:text-gray-700">
|
||||||
|
{gettext("Register")}
|
||||||
|
</.link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<.link navigate="/users/log_in" class="text-gray-900 hover:text-gray-700">
|
||||||
|
{gettext("Log in")}
|
||||||
|
</.link>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<.header dev_mode={Application.get_env(:aprsme, :dev_routes, false)} />
|
<.header dev_mode={Application.get_env(:aprsme, :dev_routes, false)} current_user={@current_user} />
|
||||||
<main
|
<main
|
||||||
id="main-content"
|
id="main-content"
|
||||||
class={if assigns[:map_page], do: "min-h-screen bg-base-100", else: "bg-base-100"}
|
class={if assigns[:map_page], do: "min-h-screen bg-base-100", else: "bg-base-100"}
|
||||||
|
|
|
||||||
|
|
@ -906,7 +906,7 @@ defmodule AprsmeWeb.MapLive.Index do
|
||||||
</svg>
|
</svg>
|
||||||
<span class="font-medium">{gettext("Navigation")}</span>
|
<span class="font-medium">{gettext("Navigation")}</span>
|
||||||
</div>
|
</div>
|
||||||
<.navigation variant={:vertical} class="text-sm" />
|
<.navigation variant={:vertical} class="text-sm" current_user={@current_user} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Deployment Information -->
|
<!-- Deployment Information -->
|
||||||
|
|
|
||||||
|
|
@ -6,25 +6,61 @@ defmodule AprsmeWeb.UserConfirmationInstructionsLive do
|
||||||
|
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<.header />
|
<div class="hero min-h-screen bg-base-200">
|
||||||
|
<div class="hero-content text-center">
|
||||||
|
<div class="max-w-md">
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h1 class="card-title text-3xl mb-4 justify-center">
|
||||||
|
Resend confirmation instructions
|
||||||
|
</h1>
|
||||||
|
<p class="text-base-content/70 mb-6">
|
||||||
|
Enter your email address to receive a new confirmation link
|
||||||
|
</p>
|
||||||
|
|
||||||
<.simple_form
|
<.simple_form
|
||||||
:let={f}
|
:let={_f}
|
||||||
for={%{}}
|
for={%{}}
|
||||||
as={:user}
|
as={:user}
|
||||||
id="resend_confirmation_form"
|
id="resend_confirmation_form"
|
||||||
phx-submit="send_instructions"
|
phx-submit="send_instructions"
|
||||||
>
|
>
|
||||||
<.input field={{f, :email}} type="email" label="Email" required />
|
<div class="form-control w-full">
|
||||||
<:actions>
|
<label class="label">
|
||||||
<.button phx-disable-with="Sending...">Resend confirmation instructions</.button>
|
<span class="label-text">Email</span>
|
||||||
</:actions>
|
</label>
|
||||||
</.simple_form>
|
<input
|
||||||
|
type="email"
|
||||||
|
name="user[email]"
|
||||||
|
class="input input-bordered w-full bg-base-100 text-base-content"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p>
|
<div class="form-control mt-6">
|
||||||
<.link navigate={~p"/users/register"}>Register</.link>
|
<button type="submit" class="btn btn-primary w-full" phx-disable-with="Sending...">
|
||||||
| <.link navigate={~p"/users/log_in"}>Log in</.link>
|
Resend confirmation instructions
|
||||||
</p>
|
</button>
|
||||||
|
</div>
|
||||||
|
</.simple_form>
|
||||||
|
|
||||||
|
<div class="divider">OR</div>
|
||||||
|
|
||||||
|
<div class="text-center text-sm">
|
||||||
|
<.link navigate={~p"/users/register"} class="link link-primary">
|
||||||
|
Register
|
||||||
|
</.link>
|
||||||
|
|
|
||||||
|
<.link navigate={~p"/users/log_in"} class="link link-primary">
|
||||||
|
Log in
|
||||||
|
</.link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,49 @@ defmodule AprsmeWeb.UserConfirmationLive do
|
||||||
|
|
||||||
def render(%{live_action: :edit} = assigns) do
|
def render(%{live_action: :edit} = assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="mx-auto max-w-sm">
|
<div class="hero min-h-screen bg-base-200">
|
||||||
<.header class="text-center" />
|
<div class="hero-content text-center">
|
||||||
|
<div class="max-w-md">
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h1 class="card-title text-3xl mb-4 justify-center">Confirm your account</h1>
|
||||||
|
<p class="text-base-content/70 mb-6">Click the button below to confirm your account</p>
|
||||||
|
|
||||||
<.simple_form :let={f} for={%{}} as={:user} id="confirmation_form" phx-submit="confirm_account">
|
<.simple_form
|
||||||
<.input field={{f, :token}} type="hidden" value={@token} />
|
:let={_f}
|
||||||
<:actions>
|
for={%{}}
|
||||||
<.button phx-disable-with="Confirming..." class="w-full">Confirm my account</.button>
|
as={:user}
|
||||||
</:actions>
|
id="confirmation_form"
|
||||||
</.simple_form>
|
phx-submit="confirm_account"
|
||||||
|
>
|
||||||
|
<input type="hidden" name="user[token]" value={@token} />
|
||||||
|
|
||||||
<p class="text-center mt-4">
|
<div class="form-control mt-6">
|
||||||
<.link navigate={~p"/users/register"}>Register</.link>
|
<button
|
||||||
| <.link navigate={~p"/users/log_in"}>Log in</.link>
|
type="submit"
|
||||||
</p>
|
class="btn btn-primary w-full"
|
||||||
|
phx-disable-with="Confirming..."
|
||||||
|
>
|
||||||
|
Confirm my account
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</.simple_form>
|
||||||
|
|
||||||
|
<div class="divider">OR</div>
|
||||||
|
|
||||||
|
<div class="text-center text-sm">
|
||||||
|
<.link navigate={~p"/users/register"} class="link link-primary">
|
||||||
|
Register
|
||||||
|
</.link>
|
||||||
|
|
|
||||||
|
<.link navigate={~p"/users/log_in"} class="link link-primary">
|
||||||
|
Log in
|
||||||
|
</.link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -6,24 +6,56 @@ defmodule AprsmeWeb.UserForgotPasswordLive do
|
||||||
|
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="mx-auto max-w-sm">
|
<div class="hero min-h-screen bg-base-200">
|
||||||
<.header class="text-center" />
|
<div class="hero-content text-center">
|
||||||
|
<div class="max-w-md">
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h1 class="card-title text-3xl mb-4 justify-center">Forgot your password?</h1>
|
||||||
|
<p class="text-base-content/70 mb-6">We'll send a password reset link to your inbox</p>
|
||||||
|
|
||||||
<h2 class="text-center text-2xl font-bold mt-6 mb-2">Forgot your password?</h2>
|
<.simple_form
|
||||||
<p class="text-center text-gray-600 mb-6">We'll send a password reset link to your inbox</p>
|
:let={_f}
|
||||||
|
id="reset_password_form"
|
||||||
|
for={%{}}
|
||||||
|
as={:user}
|
||||||
|
phx-submit="send_email"
|
||||||
|
>
|
||||||
|
<div class="form-control w-full">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Email</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="user[email]"
|
||||||
|
class="input input-bordered w-full bg-base-100 text-base-content"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<.simple_form :let={f} id="reset_password_form" for={%{}} as={:user} phx-submit="send_email">
|
<div class="form-control mt-6">
|
||||||
<.input field={{f, :email}} type="email" placeholder="Email" required />
|
<button type="submit" class="btn btn-primary w-full" phx-disable-with="Sending...">
|
||||||
<:actions>
|
Send password reset instructions
|
||||||
<.button phx-disable-with="Sending..." class="w-full">
|
</button>
|
||||||
Send password reset instructions
|
</div>
|
||||||
</.button>
|
</.simple_form>
|
||||||
</:actions>
|
|
||||||
</.simple_form>
|
<div class="divider">OR</div>
|
||||||
<p class="text-center mt-4">
|
|
||||||
<.link navigate={~p"/users/register"}>Register</.link>
|
<div class="text-center text-sm">
|
||||||
| <.link navigate={~p"/users/log_in"}>Log in</.link>
|
<.link navigate={~p"/users/register"} class="link link-primary">
|
||||||
</p>
|
Register
|
||||||
|
</.link>
|
||||||
|
|
|
||||||
|
<.link navigate={~p"/users/log_in"} class="link link-primary">
|
||||||
|
Log in
|
||||||
|
</.link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -7,41 +7,88 @@ defmodule AprsmeWeb.UserLoginLive do
|
||||||
|
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="mx-auto max-w-sm">
|
<div class="hero min-h-screen bg-base-200">
|
||||||
<.header class="text-center" />
|
<div class="hero-content text-center">
|
||||||
|
<div class="max-w-md">
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h1 class="card-title text-3xl mb-4 justify-center">Sign in to account</h1>
|
||||||
|
<p class="text-base-content/70 mb-6">
|
||||||
|
Don't have an account?
|
||||||
|
<.link navigate={~p"/users/register"} class="link link-primary">
|
||||||
|
Sign up
|
||||||
|
</.link>
|
||||||
|
for an account now.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2 class="text-center text-2xl font-bold mt-6 mb-2">Sign in to account</h2>
|
<.simple_form
|
||||||
<p class="text-center text-gray-600 mb-6">
|
:let={f}
|
||||||
Don't have an account?
|
id="login_form"
|
||||||
<.link navigate={~p"/users/register"} class="font-semibold text-brand hover:underline">
|
for={@changeset}
|
||||||
Sign up
|
action={~p"/users/log_in"}
|
||||||
</.link>
|
as={:user}
|
||||||
for an account now.
|
phx-update="ignore"
|
||||||
</p>
|
>
|
||||||
|
<div class="form-control w-full">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Email</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name={Phoenix.HTML.Form.input_name(f, :email)}
|
||||||
|
value={Phoenix.HTML.Form.input_value(f, :email) || ""}
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<.simple_form
|
<div class="form-control w-full">
|
||||||
:let={f}
|
<label class="label">
|
||||||
id="login_form"
|
<span class="label-text">Password</span>
|
||||||
for={@changeset}
|
</label>
|
||||||
action={~p"/users/log_in"}
|
<input
|
||||||
as={:user}
|
type="password"
|
||||||
phx-update="ignore"
|
name={Phoenix.HTML.Form.input_name(f, :password)}
|
||||||
>
|
value={Phoenix.HTML.Form.input_value(f, :password) || ""}
|
||||||
<.input field={{f, :email}} type="email" label="Email" required />
|
class="input input-bordered w-full"
|
||||||
<.input field={{f, :password}} type="password" label="Password" required />
|
placeholder="Enter your password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<:actions :let={f}>
|
<div class="form-control">
|
||||||
<.input field={{f, :remember_me}} type="checkbox" label="Keep me logged in" />
|
<label class="label cursor-pointer justify-start">
|
||||||
<.link navigate={~p"/users/reset_password"} class="text-sm font-semibold">
|
<input
|
||||||
Forgot your password?
|
type="checkbox"
|
||||||
</.link>
|
name={Phoenix.HTML.Form.input_name(f, :remember_me)}
|
||||||
</:actions>
|
value="true"
|
||||||
<:actions>
|
class="checkbox checkbox-primary mr-2"
|
||||||
<.button phx-disable-with="Signing in..." class="w-full">
|
/>
|
||||||
Sign in <span aria-hidden="true">→</span>
|
<span class="label-text">Keep me logged in</span>
|
||||||
</.button>
|
</label>
|
||||||
</:actions>
|
</div>
|
||||||
</.simple_form>
|
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<.link navigate={~p"/users/reset_password"} class="link link-primary text-sm">
|
||||||
|
Forgot your password?
|
||||||
|
</.link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mt-6">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-primary w-full"
|
||||||
|
phx-disable-with="Signing in..."
|
||||||
|
>
|
||||||
|
Sign in →
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</.simple_form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -7,40 +7,77 @@ defmodule AprsmeWeb.UserRegistrationLive do
|
||||||
|
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="mx-auto max-w-sm">
|
<div class="hero min-h-screen bg-base-200">
|
||||||
<.header class="text-center" />
|
<div class="hero-content text-center">
|
||||||
|
<div class="max-w-md">
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h1 class="card-title text-3xl mb-4 justify-center">Register for an account</h1>
|
||||||
|
<p class="text-base-content/70 mb-6">
|
||||||
|
Already registered?
|
||||||
|
<.link navigate={~p"/users/log_in"} class="link link-primary">
|
||||||
|
Sign in
|
||||||
|
</.link>
|
||||||
|
to your account now.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2 class="text-center text-2xl font-bold mt-6 mb-2">Register for an account</h2>
|
<.simple_form
|
||||||
<p class="text-center text-gray-600 mb-6">
|
:let={f}
|
||||||
Already registered?
|
id="registration_form"
|
||||||
<.link navigate={~p"/users/log_in"} class="font-semibold text-brand hover:underline">
|
for={@changeset}
|
||||||
Sign in
|
phx-submit="save"
|
||||||
</.link>
|
phx-change="validate"
|
||||||
to your account now.
|
phx-trigger-action={@trigger_submit}
|
||||||
</p>
|
action={~p"/users/log_in?_action=registered"}
|
||||||
|
method="post"
|
||||||
|
as={:user}
|
||||||
|
>
|
||||||
|
<div :if={@changeset.action == :insert} class="alert alert-error mb-4">
|
||||||
|
<span>Oops, something went wrong! Please check the errors below.</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<.simple_form
|
<div class="form-control w-full">
|
||||||
:let={f}
|
<label class="label">
|
||||||
id="registration_form"
|
<span class="label-text">Email</span>
|
||||||
for={@changeset}
|
</label>
|
||||||
phx-submit="save"
|
<input
|
||||||
phx-change="validate"
|
type="email"
|
||||||
phx-trigger-action={@trigger_submit}
|
name={Phoenix.HTML.Form.input_name(f, :email)}
|
||||||
action={~p"/users/log_in?_action=registered"}
|
value={Phoenix.HTML.Form.input_value(f, :email) || ""}
|
||||||
method="post"
|
class="input input-bordered w-full bg-base-100 text-base-content"
|
||||||
as={:user}
|
placeholder="Enter your email"
|
||||||
>
|
required
|
||||||
<.error :if={@changeset.action == :insert}>
|
/>
|
||||||
Oops, something went wrong! Please check the errors below.
|
</div>
|
||||||
</.error>
|
|
||||||
|
|
||||||
<.input field={{f, :email}} type="email" label="Email" required />
|
<div class="form-control w-full">
|
||||||
<.input field={{f, :password}} type="password" label="Password" required />
|
<label class="label">
|
||||||
|
<span class="label-text">Password</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name={Phoenix.HTML.Form.input_name(f, :password)}
|
||||||
|
value={Phoenix.HTML.Form.input_value(f, :password) || ""}
|
||||||
|
class="input input-bordered w-full bg-base-100 text-base-content"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<:actions>
|
<div class="form-control mt-6">
|
||||||
<.button phx-disable-with="Creating account..." class="w-full">Create an account</.button>
|
<button
|
||||||
</:actions>
|
type="submit"
|
||||||
</.simple_form>
|
class="btn btn-primary w-full"
|
||||||
|
phx-disable-with="Creating account..."
|
||||||
|
>
|
||||||
|
Create an account
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</.simple_form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -6,36 +6,75 @@ defmodule AprsmeWeb.UserResetPasswordLive do
|
||||||
|
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="mx-auto max-w-sm">
|
<div class="hero min-h-screen bg-base-200">
|
||||||
<.header class="text-center" />
|
<div class="hero-content text-center">
|
||||||
|
<div class="max-w-md">
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h1 class="card-title text-3xl mb-4 justify-center">Reset Password</h1>
|
||||||
|
<p class="text-base-content/70 mb-6">Enter your new password below</p>
|
||||||
|
|
||||||
<.simple_form
|
<.simple_form
|
||||||
:let={f}
|
:let={f}
|
||||||
for={@changeset}
|
for={@changeset}
|
||||||
id="reset_password_form"
|
id="reset_password_form"
|
||||||
phx-submit="reset_password"
|
phx-submit="reset_password"
|
||||||
phx-change="validate"
|
phx-change="validate"
|
||||||
>
|
>
|
||||||
<.error :if={@changeset.action == :insert}>
|
<div :if={@changeset.action == :insert} class="alert alert-error mb-4">
|
||||||
Oops, something went wrong! Please check the errors below.
|
<span>Oops, something went wrong! Please check the errors below.</span>
|
||||||
</.error>
|
</div>
|
||||||
|
|
||||||
<.input field={{f, :password}} type="password" label="New password" required />
|
<div class="form-control w-full">
|
||||||
<.input
|
<label class="label">
|
||||||
field={{f, :password_confirmation}}
|
<span class="label-text">New password</span>
|
||||||
type="password"
|
</label>
|
||||||
label="Confirm new password"
|
<input
|
||||||
required
|
type="password"
|
||||||
/>
|
name={Phoenix.HTML.Form.input_name(f, :password)}
|
||||||
<:actions>
|
value={Phoenix.HTML.Form.input_value(f, :password) || ""}
|
||||||
<.button phx-disable-with="Resetting..." class="w-full">Reset Password</.button>
|
class="input input-bordered w-full bg-base-100 text-base-content"
|
||||||
</:actions>
|
placeholder="Enter new password"
|
||||||
</.simple_form>
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="text-center mt-4">
|
<div class="form-control w-full">
|
||||||
<.link navigate={~p"/users/register"}>Register</.link>
|
<label class="label">
|
||||||
| <.link navigate={~p"/users/log_in"}>Log in</.link>
|
<span class="label-text">Confirm new password</span>
|
||||||
</p>
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name={Phoenix.HTML.Form.input_name(f, :password_confirmation)}
|
||||||
|
value={Phoenix.HTML.Form.input_value(f, :password_confirmation) || ""}
|
||||||
|
class="input input-bordered w-full bg-base-100 text-base-content"
|
||||||
|
placeholder="Confirm new password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mt-6">
|
||||||
|
<button type="submit" class="btn btn-primary w-full" phx-disable-with="Resetting...">
|
||||||
|
Reset Password
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</.simple_form>
|
||||||
|
|
||||||
|
<div class="divider">OR</div>
|
||||||
|
|
||||||
|
<div class="text-center text-sm">
|
||||||
|
<.link navigate={~p"/users/register"} class="link link-primary">
|
||||||
|
Register
|
||||||
|
</.link>
|
||||||
|
|
|
||||||
|
<.link navigate={~p"/users/log_in"} class="link link-primary">
|
||||||
|
Log in
|
||||||
|
</.link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -6,68 +6,147 @@ defmodule AprsmeWeb.UserSettingsLive do
|
||||||
|
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<.header />
|
<div class="hero min-h-screen bg-base-200">
|
||||||
|
<div class="hero-content flex-col w-full max-w-4xl">
|
||||||
|
<div class="text-center mb-8">
|
||||||
|
<h1 class="text-4xl font-bold">Account Settings</h1>
|
||||||
|
<p class="text-base-content/70">Update your email address and password</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<.simple_form
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 w-full">
|
||||||
:let={f}
|
<!-- Change Email Section -->
|
||||||
id="email_form"
|
<div class="card bg-base-100 shadow-xl">
|
||||||
for={@email_changeset}
|
<div class="card-body">
|
||||||
phx-submit="update_email"
|
<h2 class="card-title text-2xl mb-4">Change Email</h2>
|
||||||
phx-change="validate_email"
|
|
||||||
>
|
|
||||||
<.error :if={@email_changeset.action == :insert}>
|
|
||||||
Oops, something went wrong! Please check the errors below.
|
|
||||||
</.error>
|
|
||||||
|
|
||||||
<.input field={{f, :email}} type="email" label="Email" required />
|
<.simple_form
|
||||||
|
:let={f}
|
||||||
|
id="email_form"
|
||||||
|
for={@email_changeset}
|
||||||
|
phx-submit="update_email"
|
||||||
|
phx-change="validate_email"
|
||||||
|
>
|
||||||
|
<div :if={@email_changeset.action == :insert} class="alert alert-error mb-4">
|
||||||
|
<span>Oops, something went wrong! Please check the errors below.</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<.input
|
<div class="form-control w-full">
|
||||||
field={{f, :current_password}}
|
<label class="label">
|
||||||
name="current_password"
|
<span class="label-text">Email</span>
|
||||||
id="current_password_for_email"
|
</label>
|
||||||
type="password"
|
<input
|
||||||
label="Current password"
|
type="email"
|
||||||
value={@email_form_current_password}
|
name={Phoenix.HTML.Form.input_name(f, :email)}
|
||||||
required
|
value={Phoenix.HTML.Form.input_value(f, :email) || ""}
|
||||||
/>
|
class="input input-bordered w-full bg-base-100 text-base-content"
|
||||||
<:actions>
|
placeholder="Enter your email"
|
||||||
<.button phx-disable-with="Changing...">Change Email</.button>
|
required
|
||||||
</:actions>
|
/>
|
||||||
</.simple_form>
|
</div>
|
||||||
|
|
||||||
<.header />
|
<div class="form-control w-full">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Current password</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="current_password"
|
||||||
|
id="current_password_for_email"
|
||||||
|
value={@email_form_current_password || ""}
|
||||||
|
class="input input-bordered w-full bg-base-100 text-base-content"
|
||||||
|
placeholder="Enter current password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<.simple_form
|
<div class="form-control mt-6">
|
||||||
:let={f}
|
<button type="submit" class="btn btn-primary w-full" phx-disable-with="Changing...">
|
||||||
id="password_form"
|
Change Email
|
||||||
for={@password_changeset}
|
</button>
|
||||||
action={~p"/users/log_in?_action=password_updated"}
|
</div>
|
||||||
method="post"
|
</.simple_form>
|
||||||
phx-change="validate_password"
|
</div>
|
||||||
phx-submit="update_password"
|
</div>
|
||||||
phx-trigger-action={@trigger_submit}
|
|
||||||
>
|
<!-- Change Password Section -->
|
||||||
<.error :if={@password_changeset.action == :insert}>
|
<div class="card bg-base-100 shadow-xl">
|
||||||
Oops, something went wrong! Please check the errors below.
|
<div class="card-body">
|
||||||
</.error>
|
<h2 class="card-title text-2xl mb-4">Change Password</h2>
|
||||||
|
|
||||||
<.input field={{f, :email}} type="hidden" value={@current_email} />
|
<.simple_form
|
||||||
|
:let={f}
|
||||||
|
id="password_form"
|
||||||
|
for={@password_changeset}
|
||||||
|
action={~p"/users/log_in?_action=password_updated"}
|
||||||
|
method="post"
|
||||||
|
phx-change="validate_password"
|
||||||
|
phx-submit="update_password"
|
||||||
|
phx-trigger-action={@trigger_submit}
|
||||||
|
>
|
||||||
|
<div :if={@password_changeset.action == :insert} class="alert alert-error mb-4">
|
||||||
|
<span>Oops, something went wrong! Please check the errors below.</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<.input field={{f, :password}} type="password" label="New password" required />
|
<input
|
||||||
<.input field={{f, :password_confirmation}} type="password" label="Confirm new password" />
|
type="hidden"
|
||||||
<.input
|
name={Phoenix.HTML.Form.input_name(f, :email)}
|
||||||
field={{f, :current_password}}
|
value={@current_email}
|
||||||
name="current_password"
|
/>
|
||||||
type="password"
|
|
||||||
label="Current password"
|
<div class="form-control w-full">
|
||||||
id="current_password_for_password"
|
<label class="label">
|
||||||
value={@current_password}
|
<span class="label-text">New password</span>
|
||||||
required
|
</label>
|
||||||
/>
|
<input
|
||||||
<:actions>
|
type="password"
|
||||||
<.button phx-disable-with="Changing...">Change Password</.button>
|
name={Phoenix.HTML.Form.input_name(f, :password)}
|
||||||
</:actions>
|
value={Phoenix.HTML.Form.input_value(f, :password) || ""}
|
||||||
</.simple_form>
|
class="input input-bordered w-full bg-base-100 text-base-content"
|
||||||
|
placeholder="Enter new password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control w-full">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Confirm new password</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name={Phoenix.HTML.Form.input_name(f, :password_confirmation)}
|
||||||
|
value={Phoenix.HTML.Form.input_value(f, :password_confirmation) || ""}
|
||||||
|
class="input input-bordered w-full bg-base-100 text-base-content"
|
||||||
|
placeholder="Confirm new password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control w-full">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Current password</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="current_password"
|
||||||
|
id="current_password_for_password"
|
||||||
|
value={@current_password || ""}
|
||||||
|
class="input input-bordered w-full bg-base-100 text-base-content"
|
||||||
|
placeholder="Enter current password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mt-6">
|
||||||
|
<button type="submit" class="btn btn-primary w-full" phx-disable-with="Changing...">
|
||||||
|
Change Password
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</.simple_form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,10 +51,37 @@ defmodule AprsmeWeb.Router do
|
||||||
get "/status.json", PageController, :status_json
|
get "/status.json", PageController, :status_json
|
||||||
end
|
end
|
||||||
|
|
||||||
|
## Authentication routes
|
||||||
|
|
||||||
|
scope "/", AprsmeWeb do
|
||||||
|
pipe_through [:browser, :redirect_if_user_is_authenticated]
|
||||||
|
|
||||||
|
live_session :redirect_if_user_is_authenticated,
|
||||||
|
on_mount: [{AprsmeWeb.UserAuth, :redirect_if_user_is_authenticated}, {AprsmeWeb.LocaleHook, :set_locale}] do
|
||||||
|
live "/users/register", UserRegistrationLive, :new
|
||||||
|
live "/users/log_in", UserLoginLive, :new
|
||||||
|
live "/users/reset_password", UserForgotPasswordLive, :new
|
||||||
|
live "/users/reset_password/:token", UserResetPasswordLive, :edit
|
||||||
|
end
|
||||||
|
|
||||||
|
post "/users/log_in", UserSessionController, :create
|
||||||
|
end
|
||||||
|
|
||||||
|
scope "/", AprsmeWeb do
|
||||||
|
pipe_through [:browser, :require_authenticated_user]
|
||||||
|
|
||||||
|
live_session :require_authenticated_user,
|
||||||
|
on_mount: [{AprsmeWeb.UserAuth, :ensure_authenticated}, {AprsmeWeb.LocaleHook, :set_locale}] do
|
||||||
|
live "/users/settings", UserSettingsLive, :edit
|
||||||
|
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
scope "/", AprsmeWeb do
|
scope "/", AprsmeWeb do
|
||||||
pipe_through :browser
|
pipe_through :browser
|
||||||
|
|
||||||
live_session :regular_pages, on_mount: [{AprsmeWeb.LocaleHook, :set_locale}] do
|
live_session :regular_pages,
|
||||||
|
on_mount: [{AprsmeWeb.UserAuth, :mount_current_user}, {AprsmeWeb.LocaleHook, :set_locale}] do
|
||||||
live "/status", StatusLive.Index, :index
|
live "/status", StatusLive.Index, :index
|
||||||
live "/packets", PacketsLive.Index, :index
|
live "/packets", PacketsLive.Index, :index
|
||||||
live "/packets/:callsign", PacketsLive.CallsignView, :index
|
live "/packets/:callsign", PacketsLive.CallsignView, :index
|
||||||
|
|
@ -89,30 +116,4 @@ defmodule AprsmeWeb.Router do
|
||||||
forward "/mailbox", Plug.Swoosh.MailboxPreview
|
forward "/mailbox", Plug.Swoosh.MailboxPreview
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
## Authentication routes
|
|
||||||
|
|
||||||
scope "/", AprsmeWeb do
|
|
||||||
pipe_through [:browser, :redirect_if_user_is_authenticated]
|
|
||||||
|
|
||||||
live_session :redirect_if_user_is_authenticated,
|
|
||||||
on_mount: [{AprsmeWeb.UserAuth, :redirect_if_user_is_authenticated}, {AprsmeWeb.LocaleHook, :set_locale}] do
|
|
||||||
live "/users/register", UserRegistrationLive, :new
|
|
||||||
live "/users/log_in", UserLoginLive, :new
|
|
||||||
live "/users/reset_password", UserForgotPasswordLive, :new
|
|
||||||
live "/users/reset_password/:token", UserResetPasswordLive, :edit
|
|
||||||
end
|
|
||||||
|
|
||||||
post "/users/log_in", UserSessionController, :create
|
|
||||||
end
|
|
||||||
|
|
||||||
scope "/", AprsmeWeb do
|
|
||||||
pipe_through [:browser, :require_authenticated_user]
|
|
||||||
|
|
||||||
live_session :require_authenticated_user,
|
|
||||||
on_mount: [{AprsmeWeb.UserAuth, :ensure_authenticated}, {AprsmeWeb.LocaleHook, :set_locale}] do
|
|
||||||
live "/users/settings", UserSettingsLive, :edit
|
|
||||||
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue