From f27df8d676b17aca86d6b447c230c3df4fc744d6 Mon Sep 17 00:00:00 2001
From: Adam Piontek <adam@73k.us>
Date: Sun, 14 Aug 2022 09:14:42 -0400
Subject: [PATCH] implemented optional 'allow_registration' config, with first
 registered user being pre-confirmed Admin, registration unavailable after
 that point if allow_registration: :false

---
 assets/js/_hamburger-helper.js                |  20 +-
 config/config.exs                             |   3 +-
 lib/shift73k/accounts.ex                      |   7 +
 lib/shift73k_web/live/user/registration.ex    |  34 ++-
 .../live/user/registration.html.heex          |   4 +-
 lib/shift73k_web/live/user/reset_password.ex  |   5 +
 .../live/user/reset_password.html.heex        |   4 +-
 .../plugs/ensure_allow_registration_plug.ex   |  35 +++
 lib/shift73k_web/plugs/ensure_role_plug.ex    |   3 +-
 .../plugs/ensure_user_exist_plug.ex           |  30 +++
 lib/shift73k_web/router.ex                    |  91 ++++---
 .../templates/layout/_navbar.html.heex        |  92 +++----
 .../layout/navbar/_nouser_menu.html.heex      |  23 ++
 .../layout/navbar/_user_menu.html.heex        |   2 +-
 .../templates/user_confirmation/new.html.heex |   3 +
 .../user_reset_password/new.html.heex         |   4 +-
 .../templates/user_session/new.html.heex      |   4 +-
 lib/shift73k_web/views/layout_view.ex         |   7 +-
 .../views/user_confirmation_view.ex           |   5 +
 .../views/user_reset_password_view.ex         |   5 +
 lib/shift73k_web/views/user_session_view.ex   |   5 +
 priv/repo/seeds.exs                           | 253 +++++++++---------
 22 files changed, 395 insertions(+), 244 deletions(-)
 create mode 100644 lib/shift73k_web/plugs/ensure_allow_registration_plug.ex
 create mode 100644 lib/shift73k_web/plugs/ensure_user_exist_plug.ex
 create mode 100644 lib/shift73k_web/templates/layout/navbar/_nouser_menu.html.heex

diff --git a/assets/js/_hamburger-helper.js b/assets/js/_hamburger-helper.js
index a03c992c..c9c4f479 100644
--- a/assets/js/_hamburger-helper.js
+++ b/assets/js/_hamburger-helper.js
@@ -1,12 +1,14 @@
 const togglerBtn = document.getElementById('navbarSupportedContentToggler');
 const navbarContent = document.getElementById('navbarSupportedContent');
 
-navbarContent.addEventListener('show.bs.collapse', () => {
-  console.log('opening navbar content');
-  togglerBtn.classList.toggle('is-active');
-});
-
-navbarContent.addEventListener('hide.bs.collapse', () => {
-  console.log('closing navbar content');
-  togglerBtn.classList.toggle('is-active');
-});
+if (navbarContent != null) {
+  navbarContent.addEventListener('show.bs.collapse', () => {
+    console.log('opening navbar content');
+    togglerBtn.classList.toggle('is-active');
+  });
+  
+  navbarContent.addEventListener('hide.bs.collapse', () => {
+    console.log('closing navbar content');
+    togglerBtn.classList.toggle('is-active');
+  });
+}
diff --git a/config/config.exs b/config/config.exs
index ad34667d..5d43f0c0 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -16,7 +16,8 @@ config :shift73k,
 config :shift73k, :app_global_vars,
   time_zone: "America/New_York",
   mailer_reply_to: "reply_to@example.com",
-  mailer_from: "app_name@example.com"
+  mailer_from: "app_name@example.com",
+  allow_registration: :true
 
 # Configures the endpoint
 config :shift73k, Shift73kWeb.Endpoint,
diff --git a/lib/shift73k/accounts.ex b/lib/shift73k/accounts.ex
index 199a52b3..1ca0eeef 100644
--- a/lib/shift73k/accounts.ex
+++ b/lib/shift73k/accounts.ex
@@ -108,6 +108,13 @@ defmodule Shift73k.Accounts do
 
   """
   def register_user(attrs) do
+    # If attrs has atom keys, convert to string
+    # If attrs don't include role, put default role
+    attrs =
+      attrs
+      |> Map.new(fn {k, v} -> {to_string(k), v} end)
+      |> Map.put_new("role", registration_role())
+
     %User{}
     |> User.registration_changeset(attrs)
     |> Repo.insert()
diff --git a/lib/shift73k_web/live/user/registration.ex b/lib/shift73k_web/live/user/registration.ex
index 8b86b0fe..fc0436a3 100644
--- a/lib/shift73k_web/live/user/registration.ex
+++ b/lib/shift73k_web/live/user/registration.ex
@@ -1,6 +1,6 @@
 defmodule Shift73kWeb.UserLive.Registration do
   use Shift73kWeb, :live_view
-
+  alias Shift73k.Repo
   alias Shift73k.Accounts
   alias Shift73k.Accounts.User
 
@@ -20,9 +20,7 @@ defmodule Shift73kWeb.UserLive.Registration do
       user_id: nil,
       user_return_to: Map.get(session, "user_return_to", "/"),
       messages: [
-        success: "Welcome! Your new account has been created, and you've been logged in.",
-        info:
-          "Some features may be unavailable until you confirm your email address. Check your inbox for instructions."
+        success: "Welcome! Your new account has been created, and you've been logged in."
       ]
     }
   end
@@ -35,19 +33,33 @@ defmodule Shift73kWeb.UserLive.Registration do
 
   @impl true
   def handle_event("save", %{"user" => user_params}, socket) do
+    is_first_user = !Repo.exists?(User)
     user_params
-    |> Map.put("role", Accounts.registration_role())
     |> Accounts.register_user()
     |> case do
       {:ok, user} ->
-        {:ok, _, %Swoosh.Email{} = _captured_email} =
-          Accounts.deliver_user_confirmation_instructions(
-            user,
-            &Routes.user_confirmation_url(socket, :confirm, &1)
-          )
+        # If this is the first user, we just confirm them
+        if is_first_user do
+          user |> User.confirm_changeset() |> Repo.update()
+        else
+          # Otherwise, all new users require email confirmation so we wend instructions
+          {:ok, _, %Swoosh.Email{} = _captured_email} =
+            Accounts.deliver_user_confirmation_instructions(
+              user,
+              &Routes.user_confirmation_url(socket, :confirm, &1)
+            )
+        end
+
+        login_params =
+          if is_first_user do
+            socket.assigns.login_params
+          else
+            put_in(socket.assigns.login_params, [:messages, :info], "Some features may be unavailable until you confirm your email address. Check your inbox for instructions.")
+          end
+          |> put_in([:user_id], user.id)
 
         socket
-        |> assign(login_params: %{socket.assigns.login_params | user_id: user.id})
+        |> assign(login_params: login_params)
         |> assign(trigger_submit: true)
         |> live_noreply()
 
diff --git a/lib/shift73k_web/live/user/registration.html.heex b/lib/shift73k_web/live/user/registration.html.heex
index 41f9a11d..ca00d39c 100644
--- a/lib/shift73k_web/live/user/registration.html.heex
+++ b/lib/shift73k_web/live/user/registration.html.heex
@@ -6,7 +6,7 @@
   </h2>
   <p class="lead">Create an account to manage your work shifts with us.</p>
 
-  <%= form_for @changeset, "#", [phx_change: :validate, phx_submit: :save, novalidate: true, id: "reg_form"], fn f -> %>
+  <.form let={f} for={@changeset} phx-change="validate" phx-submit="save" novalidate id="reg_form">
 
     <%= label f, :email, class: "form-label" %>
     <div class="inner-addon left-addon mb-3" phx-feedback-for={input_id(f, :email)}>
@@ -45,7 +45,7 @@
         %>
     </div>
 
-  <% end %>
+  </.form>
 
   <p>
     <%= link "Log in", to: Routes.user_session_path(@socket, :new) %> |
diff --git a/lib/shift73k_web/live/user/reset_password.ex b/lib/shift73k_web/live/user/reset_password.ex
index 2c2b801c..2aa5856c 100644
--- a/lib/shift73k_web/live/user/reset_password.ex
+++ b/lib/shift73k_web/live/user/reset_password.ex
@@ -4,6 +4,9 @@ defmodule Shift73kWeb.UserLive.ResetPassword do
   alias Shift73k.Accounts
   alias Shift73k.Accounts.User
 
+  @app_vars Application.compile_env(:shift73k, :app_global_vars, allow_registration: :true)
+  @app_allow_registration @app_vars[:allow_registration]
+
   @impl true
   def mount(_params, session, socket) do
     user = Accounts.get_user!(session["user_id"])
@@ -37,4 +40,6 @@ defmodule Shift73kWeb.UserLive.ResetPassword do
          |> assign(changeset: changeset)}
     end
   end
+
+  def allow_registration, do: @app_allow_registration
 end
diff --git a/lib/shift73k_web/live/user/reset_password.html.heex b/lib/shift73k_web/live/user/reset_password.html.heex
index f7761c69..0abfcea5 100644
--- a/lib/shift73k_web/live/user/reset_password.html.heex
+++ b/lib/shift73k_web/live/user/reset_password.html.heex
@@ -45,7 +45,9 @@
   <% end %>
 
   <p class="mt-3 is-pulled-right">
-    <%= link "Register", to: Routes.user_registration_path(@socket, :new) %> |
+    <%= if allow_registration() do %>
+      <%= link "Register", to: Routes.user_registration_path(@socket, :new) %> |
+    <% end %>
     <%= link "Log in", to: Routes.user_session_path(@socket, :new) %>
   </p>
 
diff --git a/lib/shift73k_web/plugs/ensure_allow_registration_plug.ex b/lib/shift73k_web/plugs/ensure_allow_registration_plug.ex
new file mode 100644
index 00000000..7dfa0e96
--- /dev/null
+++ b/lib/shift73k_web/plugs/ensure_allow_registration_plug.ex
@@ -0,0 +1,35 @@
+defmodule Shift73kWeb.EnsureAllowRegistrationPlug do
+  @moduledoc """
+  This plug ensures that there is at least one known User.
+  """
+  import Plug.Conn
+  import Phoenix.Controller
+
+  alias Shift73k.Repo
+  alias Shift73k.Accounts.User
+
+  @app_vars Application.compile_env(:shift73k, :app_global_vars, allow_registration: :true)
+  @app_allow_registration @app_vars[:allow_registration]
+
+  @doc false
+  @spec init(any()) :: any()
+  def init(config), do: config
+
+  @doc false
+  @spec call(Conn.t(), atom() | [atom()]) :: Conn.t()
+  def call(conn, _opts) do
+    # If there aren't even any users, or registration is allowed
+    if !Repo.exists?(User) || @app_allow_registration do
+      # We will allow registration
+      conn
+    else
+      # Otherwise,
+      # if app is configured to not allow registration,
+      # and there is a user,
+      # then we redirect to root URL
+      conn
+      |> redirect(to: "/")
+      |> halt()
+    end
+  end
+end
diff --git a/lib/shift73k_web/plugs/ensure_role_plug.ex b/lib/shift73k_web/plugs/ensure_role_plug.ex
index 9c604e11..e8ac84d5 100644
--- a/lib/shift73k_web/plugs/ensure_role_plug.ex
+++ b/lib/shift73k_web/plugs/ensure_role_plug.ex
@@ -27,8 +27,7 @@ defmodule Shift73kWeb.EnsureRolePlug do
   def call(conn, roles) do
     user_token = get_session(conn, :user_token)
 
-    (user_token &&
-       Accounts.get_user_by_session_token(user_token))
+    (user_token && Accounts.get_user_by_session_token(user_token))
     |> has_role?(roles)
     |> maybe_halt(conn)
   end
diff --git a/lib/shift73k_web/plugs/ensure_user_exist_plug.ex b/lib/shift73k_web/plugs/ensure_user_exist_plug.ex
new file mode 100644
index 00000000..3856fec9
--- /dev/null
+++ b/lib/shift73k_web/plugs/ensure_user_exist_plug.ex
@@ -0,0 +1,30 @@
+defmodule Shift73kWeb.EnsureUserExistPlug do
+  @moduledoc """
+  This plug ensures that there is at least one known User.
+  """
+  import Plug.Conn
+  import Phoenix.Controller
+
+  alias Shift73k.Repo
+  alias Shift73k.Accounts.User
+  alias Shift73kWeb.Router.Helpers, as: Routes
+
+  @doc false
+  @spec init(any()) :: any()
+  def init(config), do: config
+
+  @doc false
+  @spec call(Conn.t(), atom() | [atom()]) :: Conn.t()
+  def call(conn, _opts) do
+    # If there aren't even any users,
+    if !Repo.exists?(User) do
+      # We're just going to redirect to registration
+      conn
+      |> redirect(to: Routes.user_registration_path(conn, :new))
+      |> halt()
+    else
+      # Otherwise we proceed as normal
+      conn
+    end
+  end
+end
diff --git a/lib/shift73k_web/router.ex b/lib/shift73k_web/router.ex
index b45acda4..7ed69670 100644
--- a/lib/shift73k_web/router.ex
+++ b/lib/shift73k_web/router.ex
@@ -2,27 +2,37 @@ defmodule Shift73kWeb.Router do
   use Shift73kWeb, :router
   import Shift73kWeb.UserAuth
   alias Shift73kWeb.EnsureRolePlug
+  alias Shift73kWeb.EnsureUserExistPlug
+  alias Shift73kWeb.EnsureAllowRegistrationPlug
 
   pipeline :browser do
-    plug(:accepts, ["html"])
-    plug(:fetch_session)
-    plug(:fetch_live_flash)
-    plug(:put_root_layout, {Shift73kWeb.LayoutView, :root})
-    plug(:protect_from_forgery)
-    plug(:put_secure_browser_headers)
-    plug(:fetch_current_user)
+    plug :accepts, ["html"]
+    plug :fetch_session
+    plug :fetch_live_flash
+    plug :put_root_layout, {Shift73kWeb.LayoutView, :root}
+    plug :protect_from_forgery
+    plug :put_secure_browser_headers
+    plug :fetch_current_user
   end
 
-  pipeline :user do
-    plug(EnsureRolePlug, [:admin, :manager, :user])
+  pipeline :ensure_role_user do
+    plug EnsureRolePlug, [:admin, :manager, :user]
   end
 
-  pipeline :manager do
-    plug(EnsureRolePlug, [:admin, :manager])
+  pipeline :ensure_user_exist do
+    plug EnsureUserExistPlug
   end
 
-  pipeline :admin do
-    plug(EnsureRolePlug, :admin)
+  pipeline :ensure_allow_registration do
+    plug EnsureAllowRegistrationPlug
+  end
+
+  pipeline :ensure_role_manager do
+    plug EnsureRolePlug, [:admin, :manager]
+  end
+
+  pipeline :ensure_role_admin do
+    plug EnsureRolePlug, :admin
   end
 
   # Enables the Swoosh mailbox preview in development.
@@ -38,49 +48,54 @@ defmodule Shift73kWeb.Router do
   end
 
   scope "/", Shift73kWeb do
-    pipe_through([:browser])
+    pipe_through([:browser, :ensure_user_exist])
 
-    get("/", Redirector, to: "/assign")
+    get "/", Redirector, to: "/assign"
   end
 
   scope "/", Shift73kWeb do
-    pipe_through([:browser, :redirect_if_user_is_authenticated])
+    pipe_through [:browser, :redirect_if_user_is_authenticated, :ensure_allow_registration]
+
+    get "/users/register", UserRegistrationController, :new
+  end
+
+  scope "/", Shift73kWeb do
+    pipe_through [:browser, :redirect_if_user_is_authenticated, :ensure_user_exist]
 
     # session routes, irrelevant if user is authenticated
-    get("/users/register", UserRegistrationController, :new)
-    get("/users/log_in", UserSessionController, :new)
-    post("/users/log_in", UserSessionController, :create)
-    get("/users/reset_password", UserResetPasswordController, :new)
-    post("/users/reset_password", UserResetPasswordController, :create)
-    get("/users/reset_password/:token", UserResetPasswordController, :edit)
+    get "/users/log_in", UserSessionController, :new
+    post "/users/log_in", UserSessionController, :create
+    get "/users/reset_password", UserResetPasswordController, :new
+    post "/users/reset_password", UserResetPasswordController, :create
+    get "/users/reset_password/:token", UserResetPasswordController, :edit
   end
 
   scope "/", Shift73kWeb do
-    pipe_through([:browser, :require_authenticated_user])
+    pipe_through [:browser, :require_authenticated_user]
 
     # user settings (change email, password, calendar week start, etc)
-    live("/users/settings", UserLive.Settings, :edit)
+    live "/users/settings", UserLive.Settings, :edit
 
     # confirm email by token
-    get("/users/settings/confirm_email/:token", UserSettingsController, :confirm_email)
+    get "/users/settings/confirm_email/:token", UserSettingsController, :confirm_email
   end
 
   scope "/", Shift73kWeb do
-    pipe_through([:browser])
+    pipe_through [:browser, :ensure_user_exist]
 
     # session paths
-    delete("/users/log_out", UserSessionController, :delete)
-    get("/users/force_logout", UserSessionController, :force_logout)
-    get("/users/confirm", UserConfirmationController, :new)
-    post("/users/confirm", UserConfirmationController, :create)
-    get("/users/confirm/:token", UserConfirmationController, :confirm)
+    delete "/users/log_out", UserSessionController, :delete
+    get "/users/force_logout", UserSessionController, :force_logout
+    get "/users/confirm", UserConfirmationController, :new
+    post "/users/confirm", UserConfirmationController, :create
+    get "/users/confirm/:token", UserConfirmationController, :confirm
 
     # ics/ical route for user's shifts
-    get("/ics/:slug", UserShiftsIcsController, :index)
+    get "/ics/:slug", UserShiftsIcsController, :index
   end
 
   scope "/", Shift73kWeb do
-    pipe_through([:browser, :require_authenticated_user, :user])
+    pipe_through [:browser, :require_authenticated_user, :ensure_role_user]
 
     live "/templates", ShiftTemplateLive.Index, :index
     live "/templates/new", ShiftTemplateLive.Index, :new
@@ -98,16 +113,16 @@ defmodule Shift73kWeb.Router do
   end
 
   # scope "/", Shift73kWeb do
-  #   pipe_through([:browser, :require_authenticated_user, :admin])
+  #   pipe_through([:browser, :require_authenticated_user, :ensure_role_admin])
   # end
 
   # Users Management
   scope "/users", Shift73kWeb do
-    pipe_through([:browser, :require_authenticated_user, :manager, :require_email_confirmed])
+    pipe_through [:browser, :require_authenticated_user, :ensure_role_manager, :require_email_confirmed]
 
-    live("/", UserManagementLive.Index, :index)
-    live("/new", UserManagementLive.Index, :new)
-    live("/edit/:id", UserManagementLive.Index, :edit)
+    live "/", UserManagementLive.Index, :index
+    live "/new", UserManagementLive.Index, :new
+    live "/edit/:id", UserManagementLive.Index, :edit
     # resources "/", UserManagementController, only: [:new, :create, :edit, :update]
   end
 end
diff --git a/lib/shift73k_web/templates/layout/_navbar.html.heex b/lib/shift73k_web/templates/layout/_navbar.html.heex
index a98856e6..4c81942d 100644
--- a/lib/shift73k_web/templates/layout/_navbar.html.heex
+++ b/lib/shift73k_web/templates/layout/_navbar.html.heex
@@ -8,79 +8,63 @@
     <% end %>
     </h1>
 
-    <%= if @current_user do %>
+    <%# If there's a current user,
+        OR if there are users & we allow registration,
+        THEN we will show a full menu configuration %>
+
+    <%= if @current_user || (Repo.exists?(User) && allow_registration()) do %>
+
       <button class="hamburger hamburger--squeeze collapsed navbar-toggler" id="navbarSupportedContentToggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
         <span class="hamburger-box d-flex">
           <span class="hamburger-inner"></span>
         </span>
       </button>
-    <% else %>
-      <%= link nav_link_opts(@conn, to: Routes.user_session_path(@conn, :new), class: "btn btn-outline-light d-block d-lg-none") do %>
-        <i class="bi bi-door-open me-1"></i> Log in
-      <% end %>
-    <% end %>
 
-    <div class="collapse navbar-collapse" id="navbarSupportedContent">
+        
+      <div class="collapse navbar-collapse" id="navbarSupportedContent">
 
-      <%# nav LEFT items %>
-      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
+        <%# nav LEFT items %>
+        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
 
-        <%#= if @current_user do %>
-          <%# <li class="nav-item"> %>
-            <%#= link nav_link_opts(@conn, to: Routes.shift_template_index_path(@conn, :index), class: "nav-link") do %>
-              <%#= icon_div @conn, "bi-clock-history", [class: "icon baseline me-1"] %>
-              <%# Templates %>
-            <%# end %>
-          <%# </li> %>
-        <%# end %>
+        </ul>
 
-        <%# normal navbar link example %>
-        <%# <li class="nav-item"> %>
-          <%#= link "Properties", nav_link_opts(@conn, to: Routes.property_index_path(@conn, :index), class: "nav-link") %>
-        <%# </li> %>
 
-        <%# ACTIVE page link example %>
-        <%# <li class="nav-item">
-          <a class="nav-link active" aria-current="page" href="#">Home</a>
-        </li> %>
+        <%# nav RIGHT items %>
+        <ul class="navbar-nav">
 
-        <%# DISABLED page link example %>
-        <%# <li class="nav-item">
-          <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
-        </li> %>
+          <%= if @current_user do %>
 
-        <%# normal dropdown menu example %>
-        <%# <li class="nav-item dropdown">
-          <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownExample" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</a>
-          <ul class="dropdown-menu" aria-labelledby="navbarDropdownExample">
-            <li><a class="dropdown-item" href="#">Action</a></li>
-            <li><a class="dropdown-item" href="#">Another action</a></li>
-            <li><hr class="dropdown-divider"></li>
-            <li><a class="dropdown-item" href="#">Something else here</a></li>
-          </ul>
-        </li> %>
+            <%= render "navbar/_shifts_menu.html", assigns %>
 
-      </ul>
+            <%= render "navbar/_user_menu.html", assigns %>
+          
+          <% else %>
 
-      <%# nav RIGHT items %>
-      <ul class="navbar-nav">
+            <%= render "navbar/_nouser_menu.html", assigns %>
 
-        <%= if @current_user do %>
-
-          <%= render "navbar/_shifts_menu.html", assigns %>
-
-          <%= render "navbar/_user_menu.html", assigns %>
-
-        <% else %>
-
-          <%= link nav_link_opts(@conn, to: Routes.user_session_path(@conn, :new), class: "btn btn-outline-light d-none d-lg-block") do %>
-            <i class="bi bi-door-open me-1"></i> Log in
           <% end %>
 
+        </ul>
+
+      </div>
+
+    <%# If there's no current user,
+        AND:
+          There are no users -- [REGISTER]
+          OR no registration allowed -- [LOG IN] %>
+    <%= else %>
+
+      <%= if !Repo.exists?(User) || allow_registration() do %>
+        <%= link nav_link_opts(@conn, to: Routes.user_registration_path(@conn, :new), class: "btn btn-outline-light") do %>
+          <i class="bi bi-journal-plus"></i> Register
         <% end %>
+      <% else %>
+        <%= link nav_link_opts(@conn, to: Routes.user_session_path(@conn, :new), class: "btn btn-outline-light") do %>
+          <i class="bi bi-door-open"></i> Log in
+        <% end %>
+      <% end %>
 
-      </ul>
+    <% end %>
 
-    </div>
   </div>
 </nav>
diff --git a/lib/shift73k_web/templates/layout/navbar/_nouser_menu.html.heex b/lib/shift73k_web/templates/layout/navbar/_nouser_menu.html.heex
new file mode 100644
index 00000000..59203835
--- /dev/null
+++ b/lib/shift73k_web/templates/layout/navbar/_nouser_menu.html.heex
@@ -0,0 +1,23 @@
+<li class="nav-item dropdown">
+
+  <a href="#" class="nav-link dropdown-toggle" id="navbarDropdownUserMenu" data-bs-toggle="dropdown" aria-expanded="false">
+    <i class="bi bi-person-circle me-1"></i> Hello?
+  </a>
+
+  <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdownUserMenu">
+
+    <li>
+      <%= link nav_link_opts(@conn, to: Routes.user_registration_path(@conn, :new), class: "dropdown-item") do %>
+        <i class="bi bi-journal-plus me-1"></i> Register
+      <% end %>
+    </li>
+
+    <li>
+      <%= link nav_link_opts(@conn, to: Routes.user_session_path(@conn, :new), class: "dropdown-item") do %>
+        <i class="bi bi-door-open me-1"></i> Log in
+      <% end %>
+    </li>
+
+  </ul>
+
+</li>
diff --git a/lib/shift73k_web/templates/layout/navbar/_user_menu.html.heex b/lib/shift73k_web/templates/layout/navbar/_user_menu.html.heex
index c74a7324..191b291b 100644
--- a/lib/shift73k_web/templates/layout/navbar/_user_menu.html.heex
+++ b/lib/shift73k_web/templates/layout/navbar/_user_menu.html.heex
@@ -1,7 +1,7 @@
 <li class="nav-item dropdown">
 
   <a href="#" class="nav-link dropdown-toggle" id="navbarDropdownUserMenu" data-bs-toggle="dropdown" aria-expanded="false">
-    <i class="bi bi-person-circle me-1"></i> <%= @current_user && "Hello!" || "Hello?" %>
+    <i class="bi bi-person-circle me-1"></i> Hello!
   </a>
 
   <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdownUserMenu">
diff --git a/lib/shift73k_web/templates/user_confirmation/new.html.heex b/lib/shift73k_web/templates/user_confirmation/new.html.heex
index 1155cd44..35e22e76 100644
--- a/lib/shift73k_web/templates/user_confirmation/new.html.heex
+++ b/lib/shift73k_web/templates/user_confirmation/new.html.heex
@@ -29,6 +29,9 @@
   <% end %>
 
   <p>
+    <%= if allow_registration() do %>
+      <%= link "Register", to: Routes.user_registration_path(@conn, :new) %> |
+    <% end %>
     <%= link "Register", to: Routes.user_registration_path(@conn, :new) %> |
     <%= link "Log in", to: Routes.user_session_path(@conn, :new) %>
   </p>
diff --git a/lib/shift73k_web/templates/user_reset_password/new.html.heex b/lib/shift73k_web/templates/user_reset_password/new.html.heex
index 8bcab246..f5be0f7f 100644
--- a/lib/shift73k_web/templates/user_reset_password/new.html.heex
+++ b/lib/shift73k_web/templates/user_reset_password/new.html.heex
@@ -27,7 +27,9 @@
   <% end %>
 
   <p>
-    <%= link "Register", to: Routes.user_registration_path(@conn, :new) %> |
+    <%= if allow_registration() do %>
+      <%= link "Register", to: Routes.user_registration_path(@conn, :new) %> |
+    <% end %>
     <%= link "Log in", to: Routes.user_session_path(@conn, :new) %>
   </p>
 
diff --git a/lib/shift73k_web/templates/user_session/new.html.heex b/lib/shift73k_web/templates/user_session/new.html.heex
index 06e3f943..7d934da1 100644
--- a/lib/shift73k_web/templates/user_session/new.html.heex
+++ b/lib/shift73k_web/templates/user_session/new.html.heex
@@ -49,7 +49,9 @@
   <% end %>
 
   <p>
-    <%= link "Register", to: Routes.user_registration_path(@conn, :new) %> |
+    <%= if allow_registration() do %>
+      <%= link "Register", to: Routes.user_registration_path(@conn, :new) %> |
+    <% end %>
     <%= link "Forgot your password?", to: Routes.user_reset_password_path(@conn, :new) %>
   </p>
 
diff --git a/lib/shift73k_web/views/layout_view.ex b/lib/shift73k_web/views/layout_view.ex
index 1c857430..b35f4aaa 100644
--- a/lib/shift73k_web/views/layout_view.ex
+++ b/lib/shift73k_web/views/layout_view.ex
@@ -1,9 +1,12 @@
 defmodule Shift73kWeb.LayoutView do
   use Shift73kWeb, :view
-
+  alias Shift73k.Repo
   alias Shift73k.Accounts.User
   alias Shift73kWeb.Roles
 
+  @app_vars Application.compile_env(:shift73k, :app_global_vars, allow_registration: :true)
+  @app_allow_registration @app_vars[:allow_registration]
+
   # With a Vite.js-based workflow, we will import different asset files in development
   # and in production builds. Therefore, we will need a way to conditionally render
   # <script> tags based on Mix environment. However, since Mix is not available in
@@ -11,6 +14,8 @@ defmodule Shift73kWeb.LayoutView do
   @env Mix.env() # remember value at compile time
   def dev_env?, do: @env == :dev
 
+  def allow_registration, do: @app_allow_registration
+
   def nav_link_opts(conn, opts) do
     case Keyword.get(opts, :to) == Phoenix.Controller.current_path(conn) do
       false -> opts
diff --git a/lib/shift73k_web/views/user_confirmation_view.ex b/lib/shift73k_web/views/user_confirmation_view.ex
index 0c02ca8f..93131825 100644
--- a/lib/shift73k_web/views/user_confirmation_view.ex
+++ b/lib/shift73k_web/views/user_confirmation_view.ex
@@ -1,4 +1,9 @@
 defmodule Shift73kWeb.UserConfirmationView do
   use Shift73kWeb, :view
   alias Shift73k.Accounts.User
+
+  @app_vars Application.compile_env(:shift73k, :app_global_vars, allow_registration: :true)
+  @app_allow_registration @app_vars[:allow_registration]
+
+  def allow_registration, do: @app_allow_registration
 end
diff --git a/lib/shift73k_web/views/user_reset_password_view.ex b/lib/shift73k_web/views/user_reset_password_view.ex
index 940a55bb..3d134259 100644
--- a/lib/shift73k_web/views/user_reset_password_view.ex
+++ b/lib/shift73k_web/views/user_reset_password_view.ex
@@ -1,4 +1,9 @@
 defmodule Shift73kWeb.UserResetPasswordView do
   use Shift73kWeb, :view
   alias Shift73k.Accounts.User
+
+  @app_vars Application.compile_env(:shift73k, :app_global_vars, allow_registration: :true)
+  @app_allow_registration @app_vars[:allow_registration]
+
+  def allow_registration, do: @app_allow_registration
 end
diff --git a/lib/shift73k_web/views/user_session_view.ex b/lib/shift73k_web/views/user_session_view.ex
index 32055191..9c2fe576 100644
--- a/lib/shift73k_web/views/user_session_view.ex
+++ b/lib/shift73k_web/views/user_session_view.ex
@@ -1,4 +1,9 @@
 defmodule Shift73kWeb.UserSessionView do
   use Shift73kWeb, :view
   alias Shift73k.Accounts.User
+
+  @app_vars Application.compile_env(:shift73k, :app_global_vars, allow_registration: :true)
+  @app_allow_registration @app_vars[:allow_registration]
+
+  def allow_registration, do: @app_allow_registration
 end
diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs
index 6c143b84..116aff2b 100644
--- a/priv/repo/seeds.exs
+++ b/priv/repo/seeds.exs
@@ -14,141 +14,150 @@ alias Shift73k.Repo
 alias Shift73k.Accounts
 alias Shift73k.Accounts.User
 
-############################################################################
-## INSERTING MOCK USER DATA
 
-{:ok, _admin} =
-  Accounts.register_user(%{
-    email: "admin@company.com",
-    password: "123456789abC",
-    password_confirmation: "123456789abC",
-    role: Accounts.registration_role()
-  })
+if Mix.env() == :dev do
 
-{:ok, _user_1} =
-  Accounts.register_user(%{
-    email: "user1@company.com",
-    password: "123456789abC",
-    password_confirmation: "123456789abC",
-    role: Accounts.registration_role()
-  })
+  if System.get_env("ECTO_SEED_DB") do
 
-{:ok, _user_2} =
-  Accounts.register_user(%{
-    email: "user2@company.com",
-    password: "123456789abC",
-    password_confirmation: "123456789abC",
-    role: Accounts.registration_role()
-  })
+    ############################################################################
+    ## INSERTING MOCK USER DATA
 
-# if Mix.env() == :dev do
-this_path = Path.dirname(__ENV__.file)
-users_json = Path.join(this_path, "MOCK_DATA_users.json")
+    {:ok, _admin} =
+      Accounts.register_user(%{
+        email: "admin@company.com",
+        password: "123456789abC",
+        password_confirmation: "123456789abC",
+        role: Accounts.registration_role()
+      })
 
-count_to_take = 15
+    {:ok, _user_1} =
+      Accounts.register_user(%{
+        email: "user1@company.com",
+        password: "123456789abC",
+        password_confirmation: "123456789abC",
+        role: Accounts.registration_role()
+      })
 
-mock_users = users_json |> File.read!() |> Jason.decode!() |> Enum.take_random(count_to_take)
+    {:ok, _user_2} =
+      Accounts.register_user(%{
+        email: "user2@company.com",
+        password: "123456789abC",
+        password_confirmation: "123456789abC",
+        role: Accounts.registration_role()
+      })
 
-extra_mock_users = ~s([
-  {"email":"adam@73k.us","password":"adamadamA1","role":"admin","inserted_at":"2018-12-14T01:01:01Z","confirmed_at":true},
-  {"email":"kat@73k.us","password":"katkatA1","role":"manager","inserted_at":"2018-12-14T01:06:01Z","confirmed_at":true},
-  {"email":"babka@73k.us","password":"Babka2020","role":"user","inserted_at":"2018-12-14T01:06:01Z","confirmed_at":false},
-  {"email":"malcolm@73k.us","password":"Malc2018","role":"user","inserted_at":"2018-12-14T01:06:01Z","confirmed_at":false},
-  {"email":"casio@73k.us","password":"Casio2011","role":"user","inserted_at":"2018-12-14T01:06:01Z","confirmed_at":false}
-])
+    # if Mix.env() == :dev do
+    this_path = Path.dirname(__ENV__.file)
+    users_json = Path.join(this_path, "MOCK_DATA_users.json")
 
-# for random week_start_at values
-[head | tail] = Shift73k.weekdays()
-week_starts = [head | Enum.drop(tail, 4)]
+    count_to_take = 15
 
-mock_users =
-  extra_mock_users
-  |> Jason.decode!()
-  |> Stream.concat(mock_users)
-  |> Enum.map(fn e ->
-    add_dt = NaiveDateTime.from_iso8601!(e["inserted_at"])
+    mock_users = users_json |> File.read!() |> Jason.decode!() |> Enum.take_random(count_to_take)
 
-    %{
-      email: e["email"],
-      role: String.to_existing_atom(e["role"]),
-      hashed_password: Bcrypt.hash_pwd_salt(e["password"]),
-      week_start_at: Enum.at(week_starts, Enum.random(0..2)),
-      calendar_slug: Ecto.UUID.generate(),
-      inserted_at: add_dt,
-      updated_at: add_dt,
-      confirmed_at: (e["confirmed_at"] && NaiveDateTime.add(add_dt, 300, :second)) || nil
-    }
-  end)
+    extra_mock_users = ~s([
+      {"email":"adam@73k.us","password":"adamadamA1","role":"admin","inserted_at":"2018-12-14T01:01:01Z","confirmed_at":true},
+      {"email":"kat@73k.us","password":"katkatA1","role":"manager","inserted_at":"2018-12-14T01:06:01Z","confirmed_at":true},
+      {"email":"babka@73k.us","password":"Babka2020","role":"user","inserted_at":"2018-12-14T01:06:01Z","confirmed_at":false},
+      {"email":"malcolm@73k.us","password":"Malc2018","role":"user","inserted_at":"2018-12-14T01:06:01Z","confirmed_at":false},
+      {"email":"casio@73k.us","password":"Casio2011","role":"user","inserted_at":"2018-12-14T01:06:01Z","confirmed_at":false}
+    ])
 
-Repo.insert_all(User, mock_users)
-# end
+    # for random week_start_at values
+    [head | tail] = Shift73k.weekdays()
+    week_starts = [head | Enum.drop(tail, 4)]
 
-#####
-# shift tepmlates
-alias Shift73k.Shifts.Templates.ShiftTemplate
+    mock_users =
+      extra_mock_users
+      |> Jason.decode!()
+      |> Stream.concat(mock_users)
+      |> Enum.map(fn e ->
+        add_dt = NaiveDateTime.from_iso8601!(e["inserted_at"])
 
-shifts_json = Path.join(this_path, "MOCK_DATA_shift-templates.json")
-mock_shifts = shifts_json |> File.read!() |> Jason.decode!()
+        %{
+          email: e["email"],
+          role: String.to_existing_atom(e["role"]),
+          hashed_password: Bcrypt.hash_pwd_salt(e["password"]),
+          week_start_at: Enum.at(week_starts, Enum.random(0..2)),
+          calendar_slug: Ecto.UUID.generate(),
+          inserted_at: add_dt,
+          updated_at: add_dt,
+          confirmed_at: (e["confirmed_at"] && NaiveDateTime.add(add_dt, 300, :second)) || nil
+        }
+      end)
+
+    Repo.insert_all(User, mock_users)
+    # end
+
+    #####
+    # shift tepmlates
+    alias Shift73k.Shifts.Templates.ShiftTemplate
+
+    shifts_json = Path.join(this_path, "MOCK_DATA_shift-templates.json")
+    mock_shifts = shifts_json |> File.read!() |> Jason.decode!()
+
+    time_from_mock = fn mock_time ->
+      case String.length(mock_time) do
+        4 -> Time.from_iso8601!("T0#{mock_time}:00")
+        5 -> Time.from_iso8601!("T#{mock_time}:00")
+      end
+    end
+
+    seconds_day = 86_400
+    seconds_days_14 = seconds_day * 14
+    seconds_half_day = Integer.floor_div(seconds_day, 2)
+
+    for user <- Accounts.list_users() do
+      user_shifts =
+        mock_shifts
+        |> Enum.take_random(:rand.uniform(15) + 5)
+        |> Enum.map(fn e ->
+          seconds_to_add = :rand.uniform(seconds_days_14) + seconds_half_day
+          add_dt = NaiveDateTime.add(user.inserted_at, seconds_to_add)
+          time_start = time_from_mock.(e["time_start"])
+          shift_len_min = e["length_minutes"] || 0
+          shift_length = e["length_hours"] * 60 * 60 + shift_len_min * 60
+          time_end = Time.add(time_start, shift_length) |> Time.truncate(:second)
+
+          %{
+            subject: e["subject"],
+            description: e["description"],
+            location: e["location"],
+            time_zone: Tzdata.zone_list() |> Enum.random(),
+            time_start: time_start,
+            time_end: time_end,
+            user_id: user.id,
+            inserted_at: add_dt,
+            updated_at: add_dt
+          }
+        end)
+
+      Repo.insert_all(ShiftTemplate, user_shifts)
+    end
+
+    #####
+    # insert shifts for each user?
+    alias Shift73k.Shifts
+    alias Shift73k.Shifts.Templates
+
+    for user <- Accounts.list_users() do
+      # build a date range for the time from 120 days ago to 120 days from now
+      today = Date.utc_today()
+      date_range = Date.range(Date.add(today, -120), Date.add(today, 120))
+
+      # get 3 random shift templates for user
+      st_list = Templates.list_shift_templates_by_user(user.id) |> Enum.take_random(3)
+
+      for st <- st_list do
+        days_to_schedule = Enum.take_random(date_range, 47)
+        shift_data = ShiftTemplate.attrs(st)
+
+        days_to_schedule
+        |> Stream.map(&Map.put(shift_data, :date, &1))
+        |> Enum.map(&Repo.timestamp/1)
+        |> Shifts.create_multiple()
+      end
+    end
 
-time_from_mock = fn mock_time ->
-  case String.length(mock_time) do
-    4 -> Time.from_iso8601!("T0#{mock_time}:00")
-    5 -> Time.from_iso8601!("T#{mock_time}:00")
-  end
-end
-
-seconds_day = 86_400
-seconds_days_14 = seconds_day * 14
-seconds_half_day = Integer.floor_div(seconds_day, 2)
-
-for user <- Accounts.list_users() do
-  user_shifts =
-    mock_shifts
-    |> Enum.take_random(:rand.uniform(15) + 5)
-    |> Enum.map(fn e ->
-      seconds_to_add = :rand.uniform(seconds_days_14) + seconds_half_day
-      add_dt = NaiveDateTime.add(user.inserted_at, seconds_to_add)
-      time_start = time_from_mock.(e["time_start"])
-      shift_len_min = e["length_minutes"] || 0
-      shift_length = e["length_hours"] * 60 * 60 + shift_len_min * 60
-      time_end = Time.add(time_start, shift_length) |> Time.truncate(:second)
-
-      %{
-        subject: e["subject"],
-        description: e["description"],
-        location: e["location"],
-        time_zone: Tzdata.zone_list() |> Enum.random(),
-        time_start: time_start,
-        time_end: time_end,
-        user_id: user.id,
-        inserted_at: add_dt,
-        updated_at: add_dt
-      }
-    end)
-
-  Repo.insert_all(ShiftTemplate, user_shifts)
-end
-
-#####
-# insert shifts for each user?
-alias Shift73k.Shifts
-alias Shift73k.Shifts.Templates
-
-for user <- Accounts.list_users() do
-  # build a date range for the time from 120 days ago to 120 days from now
-  today = Date.utc_today()
-  date_range = Date.range(Date.add(today, -120), Date.add(today, 120))
-
-  # get 3 random shift templates for user
-  st_list = Templates.list_shift_templates_by_user(user.id) |> Enum.take_random(3)
-
-  for st <- st_list do
-    days_to_schedule = Enum.take_random(date_range, 47)
-    shift_data = ShiftTemplate.attrs(st)
-
-    days_to_schedule
-    |> Stream.map(&Map.put(shift_data, :date, &1))
-    |> Enum.map(&Repo.timestamp/1)
-    |> Shifts.create_multiple()
   end
+
 end