diff --git a/assets/css/app.css b/assets/css/app.css index 692996e4..b044c0a9 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -154,3 +154,30 @@ tbody tr:hover td { background-color: rgba(59, 130, 246, 0.04); } outline: 2px solid oklch(58% 0.233 277.117); outline-offset: 2px; } + +/* Global smooth transitions for interactive elements */ +a, button, input, select, textarea { + transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease; +} + +/* Dark mode body transition */ +body, .bg-gray-50, .bg-white, .dark\:bg-gray-950, .dark\:bg-gray-900 { + transition: background-color 0.2s ease; +} + +/* Password strength indicator (JS-driven via classes) */ +.password-strength-bar.strength-weak { background-color: #ef4444; } +.password-strength-bar.strength-fair { background-color: #f59e0b; } +.password-strength-bar.strength-good { background-color: #22c55e; } +.password-strength-bar.strength-strong { background-color: #16a34a; } + +/* Skeleton shimmer effect */ +@keyframes shimmer { + 0% { background-position: -200% 0; } + 100% { background-position: 200% 0; } +} +.skeleton-shimmer { + background: linear-gradient(90deg, transparent 25%, rgba(255,255,255,0.08) 50%, transparent 75%); + background-size: 200% 100%; + animation: shimmer 1.5s ease-in-out infinite; +} diff --git a/lib/towerops_web/components/core_components.ex b/lib/towerops_web/components/core_components.ex index db24e584..22a5ff13 100644 --- a/lib/towerops_web/components/core_components.ex +++ b/lib/towerops_web/components/core_components.ex @@ -134,21 +134,36 @@ defmodule ToweropsWeb.CoreComponents do """ attr :rest, :global, include: ~w(href navigate patch method download name value disabled) attr :class, :any - attr :variant, :string, values: ~w(primary danger) + attr :variant, :string, values: ~w(primary secondary danger ghost) + attr :size, :string, default: "md", values: ~w(sm md lg) + attr :loading, :boolean, default: false slot :inner_block, required: true def button(%{rest: rest} = assigns) do variants = %{ - "primary" => "bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500 dark:bg-blue-500 dark:hover:bg-blue-600", - "danger" => "bg-red-600 text-white hover:bg-red-700 focus:ring-red-500 dark:bg-red-500 dark:hover:bg-red-600", + "primary" => + "bg-blue-600 text-white hover:bg-blue-500 active:bg-blue-700 focus:ring-blue-500 shadow-md hover:shadow-lg dark:bg-blue-500 dark:hover:bg-blue-400 dark:active:bg-blue-600", + "secondary" => + "bg-white text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:ring-blue-500 dark:bg-gray-800 dark:text-gray-200 dark:ring-gray-600 dark:hover:bg-gray-700", + "danger" => + "bg-red-600 text-white hover:bg-red-500 active:bg-red-700 focus:ring-red-500 dark:bg-red-500 dark:hover:bg-red-400", + "ghost" => + "bg-transparent text-gray-700 hover:bg-gray-100 focus:ring-blue-500 shadow-none dark:text-gray-300 dark:hover:bg-gray-800", nil => "bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:ring-gray-700 dark:hover:bg-gray-700" } + sizes = %{ + "sm" => "px-2.5 py-1.5 text-xs", + "md" => "px-3.5 py-2.5 text-sm", + "lg" => "px-5 py-3 text-base" + } + assigns = assign_new(assigns, :class, fn -> [ - "inline-flex items-center gap-2 rounded-lg px-3.5 py-2.5 text-sm font-semibold shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed", + "inline-flex items-center justify-center gap-2 rounded-lg font-semibold shadow-sm transition-all duration-150 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed dark:focus:ring-offset-gray-900", + Map.fetch!(sizes, assigns.size), Map.fetch!(variants, assigns[:variant]) ] end) @@ -156,12 +171,44 @@ defmodule ToweropsWeb.CoreComponents do if rest[:href] || rest[:navigate] || rest[:patch] do ~H""" <.link class={@class} {@rest}> + + + + + + {render_slot(@inner_block)} """ else ~H""" - """ @@ -497,7 +544,11 @@ defmodule ToweropsWeb.CoreComponents do phx-update={is_struct(@rows, Phoenix.LiveView.LiveStream) && "stream"} class="divide-y divide-gray-200 bg-white dark:divide-white/10 dark:bg-gray-800/50" > - + "" end) + + ~H""" +
+ """ + end + @doc """ - A pulsing card placeholder. + A pulsing card placeholder matching stat cards. """ attr :class, :string, default: nil @@ -15,9 +24,9 @@ defmodule ToweropsWeb.Components.Skeletons do "animate-pulse rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-800/50", @class ]}> -
-
-
+ <.skel class="h-3 w-24" /> + <.skel class="mt-3 h-8 w-16" /> + <.skel class="mt-2 h-3 w-20" /> """ end @@ -30,18 +39,33 @@ defmodule ToweropsWeb.Components.Skeletons do def skeleton_table(assigns) do ~H""" -
-
-
-
+
+ +
+
+
+
+
-
-
+
+
+
+
+
@@ -59,47 +83,72 @@ defmodule ToweropsWeb.Components.Skeletons do "animate-pulse rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-800/50", @class ]}> -
-
+
+
+
+
+
+
""" end @doc """ - Dashboard loading skeleton - full placeholder for the dashboard content. + Dashboard loading skeleton — mirrors the real dashboard layout. """ def skeleton_dashboard(assigns) do ~H""" +
<.skeleton_stat :for={_ <- 1..6} />
+
-
+
+
+
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
-
+
+
-
-
+
+
+
+
diff --git a/lib/towerops_web/controllers/user_registration_html/new.html.heex b/lib/towerops_web/controllers/user_registration_html/new.html.heex index cfe3cd02..d3cada4f 100644 --- a/lib/towerops_web/controllers/user_registration_html/new.html.heex +++ b/lib/towerops_web/controllers/user_registration_html/new.html.heex @@ -1,139 +1,212 @@ -
-
- <.header> - <%= if @invitation do %> - {t_auth("Join %{organization}", organization: @invitation.organization.name)} - <% else %> - {t_auth("Register for an account")} - <% end %> - <:subtitle> - {t_auth("Already registered?")} - <.link - navigate={~p"/users/log-in"} - class="font-semibold text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300" - > - {t_auth("Log in")} - - {t_auth("to your account now.")} - - - - <%= if @invitation do %> -
-

- {t_auth("You've been invited to join %{organization} as a %{role}.", - organization: @invitation.organization.name, - role: @invitation.role - )} +

+
+ +