Much work.

This commit is contained in:
Dan Milne
2025-11-04 10:32:05 +11:00
parent c72d83acda
commit 85252a1a07
51 changed files with 1170 additions and 97 deletions

View File

@@ -54,8 +54,41 @@
<%= link_to "Projects", projects_path, class: "nav-link" %>
</li>
<li class="nav-item">
<%= link_to "Rule Sets", rule_sets_path, class: "nav-link" %>
<%= link_to "Rules", rules_path, class: "nav-link" %>
</li>
<% if user_signed_in? && current_user_admin? %>
<li class="nav-item">
<%= link_to "Users", users_path, class: "nav-link" %>
</li>
<% end %>
</ul>
<ul class="navbar-nav">
<% if user_signed_in? %>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<%= current_user.email_address %>
<span class="badge bg-secondary ms-1"><%= current_user.role %></span>
</a>
<ul class="dropdown-menu">
<% if current_user_admin? %>
<li><%= link_to "Manage Users", users_path, class: "dropdown-item" %></li>
<li><hr class="dropdown-divider"></li>
<% end %>
<li><%= link_to "Sign Out", session_path, data: { turbo_method: :delete }, class: "dropdown-item" %></li>
</ul>
</li>
<% else %>
<% if User.none? %>
<li class="nav-item">
<%= link_to "Create Admin Account", new_registration_path, class: "nav-link btn btn-success text-white" %>
</li>
<% else %>
<li class="nav-item">
<%= link_to "Sign In", new_session_path, class: "nav-link" %>
</li>
<% end %>
<% end %>
</ul>
</div>
</div>

View File

@@ -0,0 +1,21 @@
<div class="mx-auto md:w-2/3 w-full">
<% if alert = flash[:alert] %>
<p class="py-2 px-3 bg-red-50 mb-5 text-red-500 font-medium rounded-lg inline-block" id="alert"><%= alert %></p>
<% end %>
<h1 class="font-bold text-4xl">Update your password</h1>
<%= form_with url: password_path(params[:token]), method: :put, class: "contents" do |form| %>
<div class="my-5">
<%= form.password_field :password, required: true, autocomplete: "new-password", placeholder: "Enter new password", maxlength: 72, class: "block shadow-sm rounded-md border border-gray-400 focus:outline-solid focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
</div>
<div class="my-5">
<%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", placeholder: "Repeat new password", maxlength: 72, class: "block shadow-sm rounded-md border border-gray-400 focus:outline-solid focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
</div>
<div class="inline">
<%= form.submit "Save", class: "w-full sm:w-auto text-center rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white inline-block font-medium cursor-pointer" %>
</div>
<% end %>
</div>

View File

@@ -0,0 +1,17 @@
<div class="mx-auto md:w-2/3 w-full">
<% if alert = flash[:alert] %>
<p class="py-2 px-3 bg-red-50 mb-5 text-red-500 font-medium rounded-lg inline-block" id="alert"><%= alert %></p>
<% end %>
<h1 class="font-bold text-4xl">Forgot your password?</h1>
<%= form_with url: passwords_path, class: "contents" do |form| %>
<div class="my-5">
<%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address], class: "block shadow-sm rounded-md border border-gray-400 focus:outline-solid focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
</div>
<div class="inline">
<%= form.submit "Email reset instructions", class: "w-full sm:w-auto text-center rounded-lg px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white inline-block font-medium cursor-pointer" %>
</div>
<% end %>
</div>

View File

@@ -0,0 +1,6 @@
<p>
You can reset your password on
<%= link_to "this password reset page", edit_password_url(@user.password_reset_token) %>.
This link will expire in <%= distance_of_time_in_words(0, @user.password_reset_token_expires_in) %>.
</p>

View File

@@ -0,0 +1,4 @@
You can reset your password on
<%= edit_password_url(@user.password_reset_token) %>
This link will expire in <%= distance_of_time_in_words(0, @user.password_reset_token_expires_in) %>.

View File

@@ -0,0 +1,4 @@
<div>
<h1 class="font-bold text-4xl">Registrations#create</h1>
<p>Find me in app/views/registrations/create.html.erb</p>
</div>

View File

@@ -0,0 +1,61 @@
<div class="mx-auto md:w-2/3 w-full">
<% if alert = flash[:alert] %>
<p class="py-2 px-3 bg-red-50 mb-5 text-red-500 font-medium rounded-lg inline-block" id="alert"><%= alert %></p>
<% end %>
<% if notice = flash[:notice] %>
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
<% end %>
<div class="bg-blue-50 border border-blue-200 rounded-lg p-6 mb-6">
<h2 class="text-lg font-semibold text-blue-900 mb-2">🎉 Welcome to Baffle Hub!</h2>
<p class="text-blue-700">
This is the first time Baffle Hub is being set up. You'll create the initial administrator account
that will have full access to manage users, projects, and system settings.
</p>
</div>
<h1 class="font-bold text-3xl mb-6">Create Administrator Account</h1>
<%= form_with(model: @user, url: registration_path, class: "contents") do |form| %>
<% if @user.errors.any? %>
<div class="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
<h2 class="text-red-800 font-medium mb-2">
<%= pluralize(@user.errors.count, "error") %> prohibited this account from being saved:
</h2>
<ul class="list-disc list-inside text-red-700">
<% @user.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="my-5">
<%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "email", placeholder: "Enter your email address", class: "block shadow-sm rounded-md border border-gray-400 focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
</div>
<div class="my-5">
<%= form.password_field :password, required: true, autocomplete: "new-password", placeholder: "Create a password", minlength: 8, class: "block shadow-sm rounded-md border border-gray-400 focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
<p class="mt-1 text-sm text-gray-600">Minimum 8 characters</p>
</div>
<div class="my-5">
<%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", placeholder: "Confirm your password", minlength: 8, class: "block shadow-sm rounded-md border border-gray-400 focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
</div>
<div class="inline">
<%= form.submit "Create Administrator Account", class: "rounded-md px-3.5 py-2.5 bg-green-600 hover:bg-green-500 text-white font-medium cursor-pointer" %>
</div>
<% end %>
<div class="mt-8 text-sm text-gray-600">
<p class="mb-2">This administrator account will have:</p>
<ul class="list-disc list-inside space-y-1">
<li>Full system access and control</li>
<li>Ability to manage other users</li>
<li>Permission to create and manage projects</li>
<li>Access to system configuration and analytics</li>
</ul>
</div>
</div>

View File

@@ -0,0 +1,57 @@
<div class="mx-auto md:w-2/3 w-full">
<% if alert = flash[:alert] %>
<p class="py-2 px-3 bg-red-50 mb-5 text-red-500 font-medium rounded-lg inline-block" id="alert"><%= alert %></p>
<% end %>
<% if notice = flash[:notice] %>
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
<% end %>
<% if @show_registration %>
<div class="bg-green-50 border border-green-200 rounded-lg p-6 mb-6">
<h2 class="text-lg font-semibold text-green-900 mb-2">🚀 First Time Setup</h2>
<p class="text-green-700 mb-4">
No administrator account exists yet. Create the first administrator account to get started with Baffle Hub.
</p>
<%= link_to "Create Administrator Account", new_registration_path,
class: "inline-block rounded-md px-4 py-2 bg-green-600 hover:bg-green-500 text-white font-medium" %>
</div>
<div class="mt-6 text-center">
<span class="text-gray-500">or</span>
</div>
<% end %>
<h1 class="font-bold text-4xl">Sign in</h1>
<% if @show_oidc_login && !@show_registration %>
<div class="my-6">
<%= link_to "Sign in with #{@oidc_provider_name}", "/auth/oidc",
class: "w-full block text-center rounded-md px-3.5 py-2.5 bg-indigo-600 hover:bg-indigo-500 text-white font-medium cursor-pointer" %>
<div class="mt-4 text-center">
<span class="text-gray-500">or</span>
</div>
</div>
<% end %>
<%= form_with url: session_url, class: "contents" do |form| %>
<div class="my-5">
<%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address], class: "block shadow-sm rounded-md border border-gray-400 focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
</div>
<div class="my-5">
<%= form.password_field :password, required: true, autocomplete: "current-password", placeholder: "Enter your password", maxlength: 72, class: "block shadow-sm rounded-md border border-gray-400 focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
</div>
<div class="col-span-6 sm:flex sm:items-center sm:gap-4">
<div class="inline">
<%= form.submit "Sign in", class: "w-full sm:w-auto text-center rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white inline-block font-medium cursor-pointer" %>
</div>
<div class="mt-4 text-sm text-gray-500 sm:mt-0">
<%= link_to "Forgot password?", new_password_path, class: "text-gray-700 underline hover:no-underline" %>
</div>
</div>
<% end %>
</div>

View File

@@ -0,0 +1,63 @@
<div class="mx-auto md:w-2/3 w-full">
<div class="flex items-center mb-6">
<%= link_to "← Back to Users", users_path, class: "text-blue-600 hover:text-blue-800" %>
</div>
<h1 class="font-bold text-3xl mb-6">Edit User</h1>
<% if notice = flash[:notice] %>
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
<% end %>
<% if alert = flash[:alert] %>
<p class="py-2 px-3 bg-red-50 mb-5 text-red-500 font-medium rounded-lg inline-block" id="alert"><%= alert %></p>
<% end %>
<div class="bg-white shadow rounded-lg p-6">
<div class="mb-6">
<h2 class="text-lg font-medium text-gray-900 mb-4">User Information</h2>
<div class="grid grid-cols-1 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700">Email Address</label>
<div class="mt-1 text-sm text-gray-900"><%= @user.email_address %></div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">Created</label>
<div class="mt-1 text-sm text-gray-900"><%= @user.created_at.strftime("%B %d, %Y at %I:%M %p") %></div>
</div>
</div>
</div>
<%= form_with(model: @user, class: "contents") do |form| %>
<div class="mb-6">
<h2 class="text-lg font-medium text-gray-900 mb-4">Role Assignment</h2>
<div class="space-y-3">
<div class="flex items-center">
<%= form.radio_button :role, "admin", class: "h-4 w-4 text-purple-600 focus:ring-purple-500 border-gray-300" %>
<%= form.label :role_admin, "Admin", class: "ml-3 block text-sm font-medium text-gray-700" %>
<span class="ml-2 text-sm text-gray-500">- Full system access, user management, project creation</span>
</div>
<div class="flex items-center">
<%= form.radio_button :role, "user", class: "h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" %>
<%= form.label :role_user, "User", class: "ml-3 block text-sm font-medium text-gray-700" %>
<span class="ml-2 text-sm text-gray-500">- Read/write access to projects</span>
</div>
<div class="flex items-center">
<%= form.radio_button :role, "viewer", class: "h-4 w-4 text-gray-600 focus:ring-gray-500 border-gray-300" %>
<%= form.label :role_viewer, "Viewer", class: "ml-3 block text-sm font-medium text-gray-700" %>
<span class="ml-2 text-sm text-gray-500">- Read-only access to all projects</span>
</div>
</div>
</div>
<div class="flex justify-end">
<%= form.submit "Update User", class: "rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white font-medium cursor-pointer" %>
</div>
<% end %>
</div>
</div>

View File

@@ -0,0 +1,62 @@
<div class="mx-auto md:w-4/5 w-full">
<div class="flex justify-between items-center mb-6">
<h1 class="font-bold text-3xl">User Management</h1>
<div class="text-sm text-gray-600">
Total users: <%= @users.count %>
</div>
</div>
<% if notice = flash[:notice] %>
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
<% end %>
<% if alert = flash[:alert] %>
<p class="py-2 px-3 bg-red-50 mb-5 text-red-500 font-medium rounded-lg inline-block" id="alert"><%= alert %></p>
<% end %>
<div class="bg-white shadow overflow-hidden sm:rounded-md">
<ul class="divide-y divide-gray-200">
<% @users.each do |user| %>
<li>
<div class="px-4 py-4 sm:px-6 hover:bg-gray-50">
<div class="flex items-center justify-between">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="h-10 w-10 rounded-full bg-gray-300 flex items-center justify-center">
<span class="text-sm font-medium text-gray-700">
<%= user.email_address.first.upcase %>
</span>
</div>
</div>
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
<%= user.email_address %>
</div>
<div class="text-sm text-gray-500">
Joined <%= user.created_at.strftime("%B %d, %Y") %>
</div>
</div>
</div>
<div class="flex items-center space-x-2">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
<% if user.admin? %>bg-purple-100 text-purple-800
<% elsif user.viewer? %>bg-gray-100 text-gray-800
<% else %>bg-blue-100 text-blue-800<% end %>">
<%= user.role.capitalize %>
</span>
<%= link_to "Edit", edit_user_path(user),
class: "inline-flex items-center px-3 py-1.5 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" %>
</div>
</div>
</div>
</li>
<% end %>
</ul>
</div>
<% if @users.empty? %>
<div class="text-center py-12">
<p class="text-gray-500">No users found.</p>
</div>
<% end %>
</div>

View File

@@ -0,0 +1,4 @@
<div>
<h1 class="font-bold text-4xl">Users#show</h1>
<p>Find me in app/views/users/show.html.erb</p>
</div>

View File

@@ -0,0 +1,4 @@
<div>
<h1 class="font-bold text-4xl">Users#update</h1>
<p>Find me in app/views/users/update.html.erb</p>
</div>