mirror of
https://codeberg.org/JasterV/spazio-solazzo.git
synced 2026-04-26 18:20:03 +00:00
feat: build an admin dashboard (#11)
* refactor: update button colors * feat: add dashboard panel for admins only & update header styles
This commit is contained in:
parent
961b24b202
commit
bbc2f08215
16 changed files with 451 additions and 173 deletions
|
|
@ -220,3 +220,8 @@ h6 {
|
|||
.hover\:text-plum:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Fix label-text color in dark mode */
|
||||
.label-text {
|
||||
color: var(--color-base-content);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,6 +135,16 @@ defmodule SpazioSolazzo.Accounts.User do
|
|||
end
|
||||
end
|
||||
|
||||
# Make sure it is loaded in all read actions
|
||||
preparations do
|
||||
prepare build(load: [:is_admin])
|
||||
end
|
||||
|
||||
# Make sure it is loaded in all write actions
|
||||
changes do
|
||||
change load(:is_admin)
|
||||
end
|
||||
|
||||
attributes do
|
||||
uuid_primary_key :id
|
||||
|
||||
|
|
@ -152,6 +162,17 @@ defmodule SpazioSolazzo.Accounts.User do
|
|||
allow_nil? true
|
||||
public? true
|
||||
end
|
||||
|
||||
attribute :role, :atom do
|
||||
allow_nil? false
|
||||
public? false
|
||||
constraints one_of: [:customer, :admin]
|
||||
default :customer
|
||||
end
|
||||
end
|
||||
|
||||
calculations do
|
||||
calculate :is_admin, :boolean, expr(role == :admin)
|
||||
end
|
||||
|
||||
identities do
|
||||
|
|
|
|||
38
lib/spazio_solazzo_web/components/admin_components.ex
Normal file
38
lib/spazio_solazzo_web/components/admin_components.ex
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
defmodule SpazioSolazzoWeb.AdminComponents do
|
||||
@moduledoc """
|
||||
Reusable components for admin pages (dashboard & tools).
|
||||
"""
|
||||
use Phoenix.Component
|
||||
|
||||
import SpazioSolazzoWeb.CoreComponents, only: [icon: 1, button: 1]
|
||||
import Phoenix.Component
|
||||
|
||||
use Phoenix.VerifiedRoutes,
|
||||
endpoint: SpazioSolazzoWeb.Endpoint,
|
||||
router: SpazioSolazzoWeb.Router,
|
||||
statics: SpazioSolazzoWeb.static_paths()
|
||||
|
||||
@doc """
|
||||
Cards displayed for each tool available to admins
|
||||
"""
|
||||
attr :id, :string, doc: "optional id for the tool"
|
||||
attr :title, :string
|
||||
attr :icon, :string, doc: "Icon used to represent the type of tool"
|
||||
attr :description, :string
|
||||
|
||||
def tool_card(assigns) do
|
||||
~H"""
|
||||
<div class="card bg-base-100 text-base-content shadow-xl border border-base-200 hover:shadow-2xl transition-shadow cursor-pointer">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title flex items-center gap-2">
|
||||
<.icon name={@icon} class="size-6 text-secondary" /> {@title}
|
||||
</h2>
|
||||
<p class="text-base-content/70 mt-2">{@description}</p>
|
||||
<div class="card-actions justify-end mt-4">
|
||||
<.button class="btn btn-primary btn-sm">Open</.button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
|
@ -31,6 +31,26 @@ defmodule SpazioSolazzoWeb.CoreComponents do
|
|||
|
||||
alias Phoenix.LiveView.JS
|
||||
|
||||
@doc """
|
||||
Renders a back-to navigation link
|
||||
"""
|
||||
|
||||
attr :navigate, :string
|
||||
attr :value, :string
|
||||
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container"
|
||||
|
||||
def back_to_link(assigns) do
|
||||
~H"""
|
||||
<.link
|
||||
class="mb-6 text-sm font-medium text-neutral hover:text-secondary transition-colors inline-flex items-center gap-2"
|
||||
navigate={@navigate}
|
||||
{@rest}
|
||||
>
|
||||
<.icon name="hero-arrow-left" class="w-5 h-5" />{@value}
|
||||
</.link>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Renders flash notices.
|
||||
|
||||
|
|
@ -223,7 +243,8 @@ defmodule SpazioSolazzoWeb.CoreComponents do
|
|||
checked={@checked}
|
||||
class={@class || "checkbox checkbox-sm"}
|
||||
{@rest}
|
||||
/>{@label}
|
||||
/>
|
||||
<span class="label-text">{@label}</span>
|
||||
</span>
|
||||
</label>
|
||||
<.error :for={msg <- @errors}>{msg}</.error>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ defmodule SpazioSolazzoWeb.LandingComponents do
|
|||
"""
|
||||
use Phoenix.Component
|
||||
|
||||
import SpazioSolazzoWeb.CoreComponents, only: [icon: 1]
|
||||
import SpazioSolazzoWeb.CoreComponents, only: [icon: 1, back_to_link: 1]
|
||||
import Phoenix.Component
|
||||
|
||||
use Phoenix.VerifiedRoutes,
|
||||
|
|
@ -131,14 +131,10 @@ defmodule SpazioSolazzoWeb.LandingComponents do
|
|||
~H"""
|
||||
<section class="relative pt-6 md:pt-10 pb-16 px-6 bg-base-100">
|
||||
<div class="mx-auto max-w-[1200px]">
|
||||
<div class="mb-6 flex items-center gap-2 text-sm text-neutral">
|
||||
<.link
|
||||
navigate={~p"/"}
|
||||
class="hover:text-primary transition-colors flex items-center gap-1"
|
||||
>
|
||||
<.icon name="hero-arrow-left" class="w-4 h-4" /> Back to Home
|
||||
</.link>
|
||||
</div>
|
||||
<.back_to_link
|
||||
navigate={~p"/"}
|
||||
value="Back to Home"
|
||||
/>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-16 items-center">
|
||||
<div class="order-2 lg:order-1 flex flex-col gap-6">
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -38,8 +38,85 @@ defmodule SpazioSolazzoWeb.Layouts do
|
|||
slot :inner_block, required: true
|
||||
|
||||
def app(assigns) do
|
||||
current_year = Date.utc_today().year
|
||||
|
||||
assigns = assign(assigns, :current_year, current_year)
|
||||
|
||||
~H"""
|
||||
<.app_header current_user={@current_user} />
|
||||
<header class="sticky top-0 z-50 navbar bg-base-100 shadow-sm dark:shadow-white/40 pr-4 pl-4 text-base-content">
|
||||
<div class="navbar-start">
|
||||
<.link navigate="/" class="flex items-center gap-3 hover:opacity-80 transition-opacity">
|
||||
<img src="/images/logo.png" alt="Spazio Solazzo" class="h-8" />
|
||||
</.link>
|
||||
</div>
|
||||
<div class="navbar-center hidden md:flex">
|
||||
<ul class="menu menu-horizontal px-1">
|
||||
<%= if @current_user && @current_user.is_admin do %>
|
||||
<li>
|
||||
<.link class="dark:hover:bg-secondary/20" navigate={~p"/admin/dashboard"}>
|
||||
Dashboard
|
||||
</.link>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="navbar-end flex gap-3">
|
||||
<.theme_toggle />
|
||||
|
||||
<%= if @current_user do %>
|
||||
<%!-- Desktop Menu --%>
|
||||
<div class="hidden md:flex items-center gap-3">
|
||||
<.link
|
||||
navigate={~p"/profile"}
|
||||
class="btn btn-circle btn-outline text-secondary hover:bg-info/10"
|
||||
aria-label="Profile"
|
||||
>
|
||||
<.icon name="hero-user" class="size-6" />
|
||||
</.link>
|
||||
</div>
|
||||
<% else %>
|
||||
<.link navigate={~p"/sign-in"} id="sign-in-link" class="btn btn-secondary btn-sm">
|
||||
Sign In
|
||||
</.link>
|
||||
<% end %>
|
||||
|
||||
<%!-- Mobile menu --%>
|
||||
<%= if @current_user do %>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div
|
||||
tabindex="0"
|
||||
role="button"
|
||||
class="btn btn-ghost p-2 hover:bg-secondary/20 border-none"
|
||||
>
|
||||
<.menu_svg />
|
||||
</div>
|
||||
<ul
|
||||
tabindex="-1"
|
||||
class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow dark:shadow-none dark:border dark:border-white/40 bg-base-100 rounded-box w-52"
|
||||
>
|
||||
<%= if @current_user && @current_user.is_admin do %>
|
||||
<li class="md:hidden">
|
||||
<.link navigate={~p"/admin/dashboard"}>
|
||||
<.icon name="hero-squares-2x2" class="size-4" /> Dashboard
|
||||
</.link>
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<li class="md:hidden">
|
||||
<.link navigate={~p"/profile"}>
|
||||
<.icon name="hero-user" class="size-4" /> Profile
|
||||
</.link>
|
||||
</li>
|
||||
<li>
|
||||
<.link href={~p"/sign-out"} id="mobile-sign-out-link" class="text-error">
|
||||
<.icon name="hero-arrow-right-on-rectangle" class="size-4" /> Sign Out
|
||||
</.link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="bg-base-100 flex-1 relative transition-colors duration-300">
|
||||
{render_slot(@inner_block)}
|
||||
|
|
@ -47,156 +124,6 @@ defmodule SpazioSolazzoWeb.Layouts do
|
|||
|
||||
<.flash_group flash={@flash} />
|
||||
|
||||
<.footer />
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Shows the flash group with standard titles and content.
|
||||
|
||||
## Examples
|
||||
|
||||
<.flash_group flash={@flash} />
|
||||
"""
|
||||
attr :flash, :map, required: true, doc: "the map of flash messages"
|
||||
attr :id, :string, default: "flash-group", doc: "the optional id of flash container"
|
||||
|
||||
def flash_group(assigns) do
|
||||
~H"""
|
||||
<div id={@id} aria-live="polite">
|
||||
<.flash kind={:info} flash={@flash} />
|
||||
<.flash kind={:error} flash={@flash} />
|
||||
|
||||
<.flash
|
||||
id="client-error"
|
||||
kind={:error}
|
||||
title={gettext("We can't find the internet")}
|
||||
phx-disconnected={show(".phx-client-error #client-error") |> JS.remove_attribute("hidden")}
|
||||
phx-connected={hide("#client-error") |> JS.set_attribute({"hidden", ""})}
|
||||
hidden
|
||||
>
|
||||
{gettext("Attempting to reconnect")}
|
||||
<.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
|
||||
</.flash>
|
||||
|
||||
<.flash
|
||||
id="server-error"
|
||||
kind={:error}
|
||||
title={gettext("Something went wrong!")}
|
||||
phx-disconnected={show(".phx-server-error #server-error") |> JS.remove_attribute("hidden")}
|
||||
phx-connected={hide("#server-error") |> JS.set_attribute({"hidden", ""})}
|
||||
hidden
|
||||
>
|
||||
{gettext("Attempting to reconnect")}
|
||||
<.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
|
||||
</.flash>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Provides dark vs light theme toggle based on themes defined in app.css.
|
||||
|
||||
See <head> in root.html.heex which applies the theme before page load.
|
||||
"""
|
||||
def theme_toggle(assigns) do
|
||||
~H"""
|
||||
<button
|
||||
class="p-2 rounded-full hover:bg-base-200 text-neutral transition-colors"
|
||||
phx-click={
|
||||
JS.dispatch("phx:set-theme",
|
||||
detail: %{theme: "toggle"}
|
||||
)
|
||||
}
|
||||
title="Toggle Dark Mode"
|
||||
>
|
||||
<.icon name="hero-sun" class="size-5 [[data-theme=dark]_&]:hidden" />
|
||||
<.icon name="hero-moon" class="size-5 hidden [[data-theme=dark]_&]:block" />
|
||||
</button>
|
||||
"""
|
||||
end
|
||||
|
||||
attr :current_user, :map,
|
||||
default: nil,
|
||||
doc: "the current authenticated user"
|
||||
|
||||
defp app_header(assigns) do
|
||||
~H"""
|
||||
<header class="sticky top-0 z-50 w-full border-b border-base-200 bg-base-100 backdrop-blur-md px-6 py-4">
|
||||
<div class="mx-auto flex h-10 max-w-[1200px] items-center justify-between">
|
||||
<.link navigate="/" class="flex items-center gap-3 hover:opacity-80 transition-opacity">
|
||||
<img src="/images/logo.png" alt="Spazio Solazzo" class="h-8" />
|
||||
</.link>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<.theme_toggle />
|
||||
<%= if @current_user do %>
|
||||
<%!-- Desktop menu --%>
|
||||
<div class="hidden md:flex items-center gap-3">
|
||||
<.link
|
||||
navigate={~p"/profile"}
|
||||
class="btn btn-circle btn-outline text-primary hover:bg-info/10"
|
||||
>
|
||||
<.icon name="hero-user" class="size-6" />
|
||||
</.link>
|
||||
<.link
|
||||
href={~p"/sign-out"}
|
||||
id="sign-out-link"
|
||||
class="btn btn-outline btn-error btn-sm hover:text-error hover:bg-error/10"
|
||||
>
|
||||
Sign Out
|
||||
</.link>
|
||||
</div>
|
||||
<%!-- Mobile menu button --%>
|
||||
<button
|
||||
phx-click={JS.toggle(to: "#mobile-menu")}
|
||||
class="btn btn-ghost btn-sm md:hidden text-neutral"
|
||||
id="mobile-menu-button"
|
||||
>
|
||||
<.icon name="hero-bars-3" class="size-6" />
|
||||
</button>
|
||||
<% else %>
|
||||
<.link navigate={~p"/sign-in"} id="sign-in-link" class="btn btn-secondary btn-sm">
|
||||
Sign In
|
||||
</.link>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<%!-- Mobile dropdown menu --%>
|
||||
<%= if @current_user do %>
|
||||
<div
|
||||
id="mobile-menu"
|
||||
class="md:hidden absolute top-full right-0 left-0 mt-2 mx-6 bg-base-100 border border-base-200 rounded-xl shadow-lg overflow-hidden"
|
||||
style="display: none;"
|
||||
>
|
||||
<div class="menu">
|
||||
<.link
|
||||
navigate={~p"/profile"}
|
||||
phx-click={JS.hide(to: "#mobile-menu")}
|
||||
class="flex items-center gap-3 px-4 py-3 text-sm font-medium hover:bg-accent/10 text-neutral transition-colors"
|
||||
>
|
||||
<.icon name="hero-user" class="size-5 text-primary" /> Profile
|
||||
</.link>
|
||||
<.link
|
||||
href={~p"/sign-out"}
|
||||
id="mobile-sign-out-link"
|
||||
class="flex items-center gap-3 px-4 py-3 text-sm font-medium hover:bg-error/10 text-error transition-colors border-t border-base-200"
|
||||
>
|
||||
<.icon name="hero-arrow-right-on-rectangle" class="size-5" /> Sign Out
|
||||
</.link>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</header>
|
||||
"""
|
||||
end
|
||||
|
||||
defp footer(assigns) do
|
||||
current_year = Date.utc_today().year
|
||||
|
||||
assigns = assign(assigns, :current_year, current_year)
|
||||
|
||||
~H"""
|
||||
<footer class="footer border-t border-base-200 bg-base-100 py-12 px-6">
|
||||
<div class="mx-auto max-w-[1200px] w-full flex flex-col md:flex-row justify-between gap-8">
|
||||
<div class="flex flex-col gap-4 max-w-sm">
|
||||
|
|
@ -286,4 +213,88 @@ defmodule SpazioSolazzoWeb.Layouts do
|
|||
</footer>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Shows the flash group with standard titles and content.
|
||||
|
||||
## Examples
|
||||
|
||||
<.flash_group flash={@flash} />
|
||||
"""
|
||||
attr :flash, :map, required: true, doc: "the map of flash messages"
|
||||
attr :id, :string, default: "flash-group", doc: "the optional id of flash container"
|
||||
|
||||
def flash_group(assigns) do
|
||||
~H"""
|
||||
<div id={@id} aria-live="polite">
|
||||
<.flash kind={:info} flash={@flash} />
|
||||
<.flash kind={:error} flash={@flash} />
|
||||
|
||||
<.flash
|
||||
id="client-error"
|
||||
kind={:error}
|
||||
title={gettext("We can't find the internet")}
|
||||
phx-disconnected={show(".phx-client-error #client-error") |> JS.remove_attribute("hidden")}
|
||||
phx-connected={hide("#client-error") |> JS.set_attribute({"hidden", ""})}
|
||||
hidden
|
||||
>
|
||||
{gettext("Attempting to reconnect")}
|
||||
<.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
|
||||
</.flash>
|
||||
|
||||
<.flash
|
||||
id="server-error"
|
||||
kind={:error}
|
||||
title={gettext("Something went wrong!")}
|
||||
phx-disconnected={show(".phx-server-error #server-error") |> JS.remove_attribute("hidden")}
|
||||
phx-connected={hide("#server-error") |> JS.set_attribute({"hidden", ""})}
|
||||
hidden
|
||||
>
|
||||
{gettext("Attempting to reconnect")}
|
||||
<.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
|
||||
</.flash>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Provides dark vs light theme toggle based on themes defined in app.css.
|
||||
|
||||
See <head> in root.html.heex which applies the theme before page load.
|
||||
"""
|
||||
def theme_toggle(assigns) do
|
||||
~H"""
|
||||
<button
|
||||
class="p-2 rounded-full hover:bg-base-200 text-neutral transition-colors cursor-pointer"
|
||||
phx-click={
|
||||
JS.dispatch("phx:set-theme",
|
||||
detail: %{theme: "toggle"}
|
||||
)
|
||||
}
|
||||
title="Toggle Dark Mode"
|
||||
>
|
||||
<.icon name="hero-sun" class="size-5 [[data-theme=dark]_&]:hidden" />
|
||||
<.icon name="hero-moon" class="size-5 hidden [[data-theme=dark]_&]:block" />
|
||||
</button>
|
||||
"""
|
||||
end
|
||||
|
||||
defp menu_svg(assigns) do
|
||||
~H"""
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6h16M4 12h8m-8 6h16"
|
||||
/>
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
|
|
|||
21
lib/spazio_solazzo_web/live/admin/dashboard_live.ex
Normal file
21
lib/spazio_solazzo_web/live/admin/dashboard_live.ex
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
defmodule SpazioSolazzoWeb.Admin.DashboardLive do
|
||||
@moduledoc """
|
||||
Admin dashboard home page. Lists the available tools that admins have
|
||||
"""
|
||||
|
||||
use SpazioSolazzoWeb, :live_view
|
||||
|
||||
alias SpazioSolazzo.BookingSystem
|
||||
import SpazioSolazzoWeb.AdminComponents
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, coworking_space} = BookingSystem.get_space_by_slug("coworking", not_found_error?: false)
|
||||
{:ok, meeting_space} = BookingSystem.get_space_by_slug("meeting", not_found_error?: false)
|
||||
|
||||
{:ok,
|
||||
assign(socket,
|
||||
coworking_space: coworking_space,
|
||||
meeting_space: meeting_space
|
||||
)}
|
||||
end
|
||||
end
|
||||
28
lib/spazio_solazzo_web/live/admin/dashboard_live.html.heex
Normal file
28
lib/spazio_solazzo_web/live/admin/dashboard_live.html.heex
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<Layouts.app flash={@flash} current_user={@current_user}>
|
||||
<div class="mx-auto max-w-[1200px] px-6 py-12">
|
||||
<.back_to_link
|
||||
navigate={~p"/"}
|
||||
value="Back to Home"
|
||||
/>
|
||||
|
||||
<h1 class="text-3xl text-base-content font-bold mb-8">Admin Dashboard</h1>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<%= if @meeting_space do %>
|
||||
<.tool_card
|
||||
title={@meeting_space.name}
|
||||
description="Create walk-in bookings for the space"
|
||||
icon="hero-user-group"
|
||||
/>
|
||||
<% end %>
|
||||
|
||||
<%= if @coworking_space do %>
|
||||
<.tool_card
|
||||
title={@coworking_space.name}
|
||||
description="Create walk-in bookings for the space"
|
||||
icon="hero-user-group"
|
||||
/>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</Layouts.app>
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
<Layouts.app flash={@flash} current_user={@current_user}>
|
||||
<section class="mx-auto max-w-[1200px] px-6 py-10">
|
||||
<div class="mb-10">
|
||||
<.link
|
||||
<div class="mb-4">
|
||||
<.back_to_link
|
||||
navigate={"/#{@space.slug}"}
|
||||
class="inline-flex items-center gap-2 text-sm font-medium text-neutral hover:text-secondary transition-colors"
|
||||
>
|
||||
<.icon name="hero-arrow-left" class="w-5 h-5" /> Back to {@space.name}
|
||||
</.link>
|
||||
value={"Back to #{@space.name}"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="text-center mb-12">
|
||||
|
|
|
|||
|
|
@ -118,14 +118,14 @@ defmodule SpazioSolazzoWeb.BookingFormLiveComponent do
|
|||
<div class="mt-6 flex items-center gap-3">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary flex-1 rounded-2xl"
|
||||
class="btn btn-secondary flex-1 rounded-2xl"
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
phx-click={@on_cancel}
|
||||
class="btn btn-ghost btn-secondary dark:text-white flex-1 rounded-2xl"
|
||||
class="btn btn-ghost btn-primary dark:text-white flex-1 rounded-2xl"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<Layouts.app flash={@flash} current_user={@current_user}>
|
||||
<div class="relative flex min-h-screen flex-col bg-base-100">
|
||||
<main class="flex-1 bg-base-100">
|
||||
<div class="flex-1 bg-base-100">
|
||||
<section class="bg-base-100">
|
||||
<div class="mx-auto max-w-[1200px] px-6 pt-25 pb-20 text-center bg-base-100">
|
||||
<div class="flex justify-center">
|
||||
|
|
@ -100,6 +100,6 @@
|
|||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</Layouts.app>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
<Layouts.app flash={@flash} current_user={@current_user}>
|
||||
<div class="mx-auto max-w-[800px] px-6 py-12">
|
||||
<div class="mb-10">
|
||||
<.back_to_link
|
||||
navigate={~p"/"}
|
||||
value="Back to Home"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-10 text-center">
|
||||
<h1 class="text-4xl font-black text-base-content tracking-tight">
|
||||
User Profile
|
||||
|
|
@ -71,7 +78,7 @@
|
|||
<div class="mt-8 pt-8 border-t border-base-200 flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary rounded-xl shadow-lg cursor-pointer"
|
||||
class="btn btn-secondary rounded-xl shadow-lg cursor-pointer"
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
|
|
@ -165,7 +172,7 @@
|
|||
<button
|
||||
type="button"
|
||||
phx-click="hide_delete_modal"
|
||||
class="btn btn-ghost rounded-xl cursor-pointer"
|
||||
class="btn btn-ghost text-base-content rounded-xl cursor-pointer"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -36,4 +36,14 @@ defmodule SpazioSolazzoWeb.LiveUserAuth do
|
|||
{:cont, assign(socket, :current_user, nil)}
|
||||
end
|
||||
end
|
||||
|
||||
def on_mount(:live_admin_required, _params, _session, socket) do
|
||||
case socket.assigns[:current_user] do
|
||||
%{is_admin: true} ->
|
||||
{:cont, socket}
|
||||
|
||||
_ ->
|
||||
{:halt, Phoenix.LiveView.redirect(socket, to: ~p"/")}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -56,6 +56,13 @@ defmodule SpazioSolazzoWeb.Router do
|
|||
live "/book/asset/:asset_id", AssetBookingLive
|
||||
live "/profile", ProfileLive
|
||||
end
|
||||
|
||||
ash_authentication_live_session :admin_routes,
|
||||
on_mount: [
|
||||
{SpazioSolazzoWeb.LiveUserAuth, :live_admin_required}
|
||||
] do
|
||||
live "/admin/dashboard", Admin.DashboardLive
|
||||
end
|
||||
end
|
||||
|
||||
# Enable LiveDashboard and Swoosh mailbox preview in development
|
||||
|
|
|
|||
21
priv/repo/migrations/20260118203753_add_role_to_users.exs
Normal file
21
priv/repo/migrations/20260118203753_add_role_to_users.exs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
defmodule SpazioSolazzo.Repo.Migrations.AddRoleToUsers do
|
||||
@moduledoc """
|
||||
Updates resources based on their most recent snapshots.
|
||||
|
||||
This file was autogenerated with `mix ash_postgres.generate_migrations`
|
||||
"""
|
||||
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
alter table(:users) do
|
||||
add :role, :text, null: false, default: "customer"
|
||||
end
|
||||
end
|
||||
|
||||
def down do
|
||||
alter table(:users) do
|
||||
remove :role
|
||||
end
|
||||
end
|
||||
end
|
||||
94
priv/resource_snapshots/repo/users/20260118203753.json
Normal file
94
priv/resource_snapshots/repo/users/20260118203753.json
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
{
|
||||
"attributes": [
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "fragment(\"gen_random_uuid()\")",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": true,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "id",
|
||||
"type": "uuid"
|
||||
},
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "email",
|
||||
"type": "citext"
|
||||
},
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "name",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "phone_number",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "\"customer\"",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "role",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"base_filter": null,
|
||||
"check_constraints": [],
|
||||
"custom_indexes": [],
|
||||
"custom_statements": [],
|
||||
"has_create_action": true,
|
||||
"hash": "0B279BA9251EA91451BB80DF86AF81ECCA5395011864BE01BE1C369420AD9C18",
|
||||
"identities": [
|
||||
{
|
||||
"all_tenants?": false,
|
||||
"base_filter": null,
|
||||
"index_name": "users_unique_email_index",
|
||||
"keys": [
|
||||
{
|
||||
"type": "atom",
|
||||
"value": "email"
|
||||
}
|
||||
],
|
||||
"name": "unique_email",
|
||||
"nils_distinct?": true,
|
||||
"where": null
|
||||
}
|
||||
],
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": null,
|
||||
"strategy": null
|
||||
},
|
||||
"repo": "Elixir.SpazioSolazzo.Repo",
|
||||
"schema": null,
|
||||
"table": "users"
|
||||
}
|
||||
Loading…
Reference in a new issue