Deployment and lifecycle management for Nix
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: highlight active nav section in main navigation bar

Add visual indicator (orange text + bottom border) to the active nav
link so users can tell which section they're on.

sow-112

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+58 -34
+23 -19
apps/sower/lib/sower_web/components/layouts.ex
··· 22 22 attr :flash, :map, required: true, doc: "the map of flash messages" 23 23 24 24 attr :current_user, :map, default: nil 25 + attr :nav_section, :atom, default: nil 25 26 26 27 slot :inner_block, required: true 27 28 ··· 52 53 <li> 53 54 <.link 54 55 navigate={~p"/gardens"} 55 - class="block rounded-md px-3 py-2 hover:text-orange-700 hover:bg-zinc-200 dark:hover:text-orange-300 dark:hover:bg-zinc-600" 56 + class={mobile_nav_link_class(@nav_section, :gardens)} 56 57 > 57 58 Gardens 58 59 </.link> ··· 60 61 <li> 61 62 <.link 62 63 navigate={~p"/seeds"} 63 - class="block rounded-md px-3 py-2 hover:text-orange-700 hover:bg-zinc-200 dark:hover:text-orange-300 dark:hover:bg-zinc-600" 64 + class={mobile_nav_link_class(@nav_section, :seeds)} 64 65 > 65 66 Seeds 66 67 </.link> ··· 68 69 <li> 69 70 <.link 70 71 navigate={~p"/deployments"} 71 - class="block rounded-md px-3 py-2 hover:text-orange-700 hover:bg-zinc-200 dark:hover:text-orange-300 dark:hover:bg-zinc-600" 72 + class={mobile_nav_link_class(@nav_section, :deployments)} 72 73 > 73 74 Deployments 74 75 </.link> ··· 80 81 <li> 81 82 <.link 82 83 navigate={~p"/gardens"} 83 - class="hover:text-orange-700 dark:hover:text-orange-300" 84 + class={nav_link_class(@nav_section, :gardens)} 84 85 > 85 86 Gardens 86 87 </.link> 87 88 </li> 88 89 <li> 89 - <.link navigate={~p"/seeds"} class="hover:text-orange-700 dark:hover:text-orange-300"> 90 + <.link navigate={~p"/seeds"} class={nav_link_class(@nav_section, :seeds)}> 90 91 Seeds 91 92 </.link> 92 93 </li> 93 - <%!-- <li> --%> 94 - <%!-- <.link --%> 95 - <%!-- navigate={~p"/nix/caches"} --%> 96 - <%!-- class="hover:text-orange-700 dark:hover:text-orange-300" --%> 97 - <%!-- > --%> 98 - <%!-- Caches --%> 99 - <%!-- </.link> --%> 100 - <%!-- </li> --%> 101 - <%!-- <li> --%> 102 - <%!-- <.link navigate={~p"/forges"} class="hover:text-orange-700 dark:hover:text-orange-300"> --%> 103 - <%!-- Forges --%> 104 - <%!-- </.link> --%> 105 - <%!-- </li> --%> 106 94 <li> 107 95 <.link 108 96 navigate={~p"/deployments"} 109 - class="hover:text-orange-700 dark:hover:text-orange-300" 97 + class={nav_link_class(@nav_section, :deployments)} 110 98 > 111 99 Deployments 112 100 </.link> ··· 329 317 </.button> 330 318 </div> 331 319 """ 320 + end 321 + 322 + defp nav_link_class(current, section) when current == section do 323 + "text-orange-700 dark:text-orange-300 border-b-[3px] border-orange-700 dark:border-orange-300 pb-3.5 -mb-4" 324 + end 325 + 326 + defp nav_link_class(_current, _section) do 327 + "hover:text-orange-700 dark:hover:text-orange-300" 328 + end 329 + 330 + defp mobile_nav_link_class(current, section) when current == section do 331 + "block rounded-md px-3 py-2 text-orange-700 dark:text-orange-300 bg-zinc-200 dark:bg-zinc-600" 332 + end 333 + 334 + defp mobile_nav_link_class(_current, _section) do 335 + "block rounded-md px-3 py-2 hover:text-orange-700 hover:bg-zinc-200 dark:hover:text-orange-300 dark:hover:bg-zinc-600" 332 336 end 333 337 334 338 # Embed all files in layouts/* within this module.
+1 -1
apps/sower/lib/sower_web/live/deployment_live/index.html.heex
··· 1 - <Layouts.app flash={@flash} current_user={@current_user}> 1 + <Layouts.app flash={@flash} current_user={@current_user} nav_section={assigns[:nav_section]}> 2 2 <.header> 3 3 Listing Deployments 4 4 </.header>
+1 -1
apps/sower/lib/sower_web/live/deployment_live/show.ex
··· 6 6 @impl true 7 7 def render(assigns) do 8 8 ~H""" 9 - <Layouts.app flash={@flash} current_user={@current_user}> 9 + <Layouts.app flash={@flash} current_user={@current_user} nav_section={assigns[:nav_section]}> 10 10 <.header> 11 11 <div class="flex items-center space-x-2 min-w-0"> 12 12 <.deployment_status state={@deployment.state} result={@deployment.result} />
+1 -1
apps/sower/lib/sower_web/live/forge/connection_live/index.html.heex
··· 1 - <Layouts.app flash={@flash} current_user={@current_user}> 1 + <Layouts.app flash={@flash} current_user={@current_user} nav_section={assigns[:nav_section]}> 2 2 <.header> 3 3 Listing Forges 4 4 <:actions>
+1 -1
apps/sower/lib/sower_web/live/forge/connection_live/show.html.heex
··· 1 - <Layouts.app flash={@flash} current_user={@current_user}> 1 + <Layouts.app flash={@flash} current_user={@current_user} nav_section={assigns[:nav_section]}> 2 2 <.header> 3 3 Forge {@connection.id} 4 4 <:actions>
+1 -1
apps/sower/lib/sower_web/live/garden_live/index.html.heex
··· 1 - <Layouts.app flash={@flash} current_user={@current_user}> 1 + <Layouts.app flash={@flash} current_user={@current_user} nav_section={assigns[:nav_section]}> 2 2 <.header> 3 3 Listing Gardens 4 4 </.header>
+1 -1
apps/sower/lib/sower_web/live/garden_live/show.html.heex
··· 1 - <Layouts.app flash={@flash} current_user={@current_user}> 1 + <Layouts.app flash={@flash} current_user={@current_user} nav_section={assigns[:nav_section]}> 2 2 <.header> 3 3 <div class="flex items-center space-x-2"> 4 4 <.online state={@online} />
+1 -1
apps/sower/lib/sower_web/live/nix/cache_live/index.html.heex
··· 1 - <Layouts.app flash={@flash} current_user={@current_user}> 1 + <Layouts.app flash={@flash} current_user={@current_user} nav_section={assigns[:nav_section]}> 2 2 <.header> 3 3 Listing Nix caches 4 4 <:actions>
+1 -1
apps/sower/lib/sower_web/live/nix/cache_live/show.html.heex
··· 1 - <Layouts.app flash={@flash} current_user={@current_user}> 1 + <Layouts.app flash={@flash} current_user={@current_user} nav_section={assigns[:nav_section]}> 2 2 <.header> 3 3 Cache {@cache.id} 4 4 <:subtitle>This is a cache record from your database.</:subtitle>
+1 -1
apps/sower/lib/sower_web/live/seed_live/index.html.heex
··· 1 - <Layouts.app flash={@flash} current_user={@current_user}> 1 + <Layouts.app flash={@flash} current_user={@current_user} nav_section={assigns[:nav_section]}> 2 2 <.header> 3 3 Listing Seeds 4 4 <:subtitle>A seed is an installable configuration.</:subtitle>
+1 -1
apps/sower/lib/sower_web/live/seed_live/show.html.heex
··· 1 - <Layouts.app flash={@flash} current_user={@current_user}> 1 + <Layouts.app flash={@flash} current_user={@current_user} nav_section={assigns[:nav_section]}> 2 2 <.header> 3 3 {@seed.name} 4 4 </.header>
+1 -1
apps/sower/lib/sower_web/live/settings/access_token_live/index.html.heex
··· 1 - <Layouts.app flash={@flash} current_user={@current_user}> 1 + <Layouts.app flash={@flash} current_user={@current_user} nav_section={assigns[:nav_section]}> 2 2 <.header> 3 3 My Tokens 4 4 <:actions>
+1 -1
apps/sower/lib/sower_web/live/settings/access_token_live/show.html.heex
··· 1 - <Layouts.app flash={@flash} current_user={@current_user}> 1 + <Layouts.app flash={@flash} current_user={@current_user} nav_section={assigns[:nav_section]}> 2 2 <.header> 3 3 Token - {@access_token.sid} 4 4 <:actions>
+1 -1
apps/sower/lib/sower_web/live/settings/index_live.ex
··· 3 3 4 4 def render(assigns) do 5 5 ~H""" 6 - <Layouts.app flash={@flash} current_user={@current_user}> 6 + <Layouts.app flash={@flash} current_user={@current_user} nav_section={assigns[:nav_section]}> 7 7 <.header class="text-center"> 8 8 Account Settings 9 9 </.header>
+1 -1
apps/sower/lib/sower_web/live/subscription_live/index.html.heex
··· 1 - <Layouts.app flash={@flash} current_user={@current_user}> 1 + <Layouts.app flash={@flash} current_user={@current_user} nav_section={assigns[:nav_section]}> 2 2 <.header> 3 3 Listing Subscriptions 4 4 <:actions>
+1 -1
apps/sower/lib/sower_web/live/subscription_live/show.html.heex
··· 1 - <Layouts.app flash={@flash} current_user={@current_user}> 1 + <Layouts.app flash={@flash} current_user={@current_user} nav_section={assigns[:nav_section]}> 2 2 <.header> 3 3 {@subscription.sid} 4 4 <:actions>
+20
apps/sower/lib/sower_web/user_auth.ex
··· 160 160 socket = mount_current_user(socket, session) 161 161 162 162 if socket.assigns.current_user do 163 + socket = attach_nav_section_hook(socket) 163 164 {:cont, socket} 164 165 else 165 166 socket = ··· 179 180 {:cont, socket} 180 181 end 181 182 end 183 + 184 + defp attach_nav_section_hook(%{private: %{lifecycle: _}} = socket) do 185 + Phoenix.LiveView.attach_hook(socket, :nav_section, :handle_params, fn _params, uri, socket -> 186 + section = 187 + uri 188 + |> URI.parse() 189 + |> Map.get(:path) 190 + |> nav_section_from_path() 191 + 192 + {:cont, Phoenix.Component.assign(socket, :nav_section, section)} 193 + end) 194 + end 195 + 196 + defp attach_nav_section_hook(socket), do: socket 197 + 198 + defp nav_section_from_path("/gardens" <> _), do: :gardens 199 + defp nav_section_from_path("/seeds" <> _), do: :seeds 200 + defp nav_section_from_path("/deployments" <> _), do: :deployments 201 + defp nav_section_from_path(_), do: nil 182 202 183 203 defp mount_current_user(socket, session) do 184 204 Phoenix.Component.assign_new(socket, :current_user, fn ->