Skip to content
Snippets Groups Projects
Commit 56ec07c5 authored by Teal's avatar Teal Committed by Teal
Browse files

Add Admin UI

parent 12f2ce4c
Branches
No related tags found
1 merge request!21Add Admin UI
Pipeline #38223 passed
module Admin
class ConferencesController < ApplicationController
before_action :authenticate_user!
before_action :authorize_permission
before_action :set_conference, only: [ :edit, :update, :destroy ]
def index
@conferences = Conference.all
end
def new
@conference = Conference.new
end
def create
@conference = Conference.new(conference_params)
if @conference.save
redirect_to admin_conferences_path, notice: "Conference was successfully created."
else
render :new, status: :unprocessable_entity
end
end
def edit
end
def update
if @conference.update(conference_params)
redirect_to admin_conferences_path, notice: "Conference was successfully updated."
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@conference.destroy
redirect_to admin_conferences_path, notice: "Conference was successfully deleted."
end
private
def authorize_permission
super("manage_conferences")
end
def set_conference
@conference = Conference.find_by(slug: params[:slug])
end
def conference_params
all_params = params.require(:conference).permit(:name, :slug, :starts_at, :ends_at, :url, :time_zone, :import_job_class).to_h
data_hash = @conference&.data&.dup || {}
if params[:data].present?
params[:data].each do |key, value|
data_hash[key] = value.presence
end
end
if params[:custom_field_keys].present? && params[:custom_field_values].present?
keys = params[:custom_field_keys]
values = params[:custom_field_values]
keys.each_with_index do |key, index|
next if key.blank?
data_hash[key] = values[index].presence
end
end
all_params[:data] = data_hash
all_params
end
end
end
module Admin
class RolesController < ApplicationController
before_action :authenticate_user!
before_action :authorize_role
before_action :set_role, only: [ :edit, :update ]
def index
@roles = Role.all.includes(:permissions)
end
def edit
@permissions = Permission.all
end
def update
if @role.update(role_params)
redirect_to admin_roles_path, notice: "Role was successfully updated."
else
@permissions = Permission.all
render :edit, status: :unprocessable_entity
end
end
private
def authorize_role
super("events_admin")
end
def set_role
@role = Role.find(params[:id])
end
def role_params
params.require(:role).permit(:name, :description, permission_ids: [])
end
end
end
module Admin::RolesHelper
end
<div class="container mx-auto px-4 py-8">
<h1 class="text-2xl font-bold mb-6 dark:text-gray-200">Admin: Edit Conference: <%= @conference.name %></h1>
<%= form_with(model: [:admin, @conference], url: admin_conference_path(slug: @conference.slug), method: :patch, class: "space-y-6", data: { controller: "conference-form" }) do |form| %>
<% if @conference.errors.any? %>
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
<h2 class="font-bold"><%= pluralize(@conference.errors.count, "error") %> prohibited this conference from being saved:</h2>
<ul class="list-disc list-inside mt-2">
<% @conference.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="space-y-4">
<div>
<%= form.label :name, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.text_field :name, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white" %>
</div>
<div>
<%= form.label :slug, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.text_field :slug, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white" %>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Used in URLs. Should contain only lowercase letters, numbers, and hyphens.</p>
</div>
<div>
<%= form.label :url, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.url_field :url, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white" %>
</div>
<div>
<%= form.label :time_zone, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.time_zone_select :time_zone, nil, {}, { class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white" } %>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div>
<%= form.label :starts_at, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.datetime_local_field :starts_at, value: @conference.starts_at&.strftime('%Y-%m-%dT%H:%M'), class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white" %>
</div>
<div>
<%= form.label :ends_at, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.datetime_local_field :ends_at, value: @conference.ends_at&.strftime('%Y-%m-%dT%H:%M'), class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white" %>
</div>
</div>
<div>
<%= form.label :import_job_class, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.select :import_job_class,
[["Select Import Job Class", ""]] +
ConferencesController.available_import_job_classes.map { |class_name, display_name| [display_name, class_name] },
{},
{ class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white",
data: { conference_form_target: "importJobClass", action: "change->conference-form#importJobClassChanged" } } %>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Select the import job class to see required data fields</p>
</div>
<!-- Add hidden inputs to preserve all data values -->
<% if @conference.data.present? %>
<% @conference.data.each do |key, value| %>
<input type="hidden" name="data[<%= key %>]" value="<%= value %>" id="hidden_data_<%= key %>">
<% end %>
<% end %>
<fieldset class="mt-6 border border-gray-300 rounded-md p-4 dark:border-gray-600">
<legend class="px-2 text-sm font-medium text-gray-700 dark:text-gray-300">Required Data Fields</legend>
<div class="space-y-4" data-conference-form-target="requiredFields">
<% if @conference.import_job_class.present? %>
<% begin %>
<% klass = @conference.import_job_class.constantize %>
<% @conference.required_data_fields.each do |field| %>
<% metadata = klass.respond_to?(:field_metadata) ? (klass.field_metadata[field] || {}) : {} %>
<div class="mb-4">
<label for="data_<%= field %>" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
<%= metadata[:title] || field.humanize %><%= metadata[:required] ? ' <span class="text-red-500">*</span>'.html_safe : '' %>
</label>
<% if metadata[:description].present? %>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400"><%= metadata[:description] %></p>
<% end %>
<input
type="text"
id="data_<%= field %>"
name="data[<%= field %>]"
value="<%= @conference.data&.dig(field) %>"
placeholder="<%= metadata[:placeholder] %>"
<%= 'required' if metadata[:required] %>
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white">
</div>
<% end %>
<% rescue => e %>
<div class="text-red-500">Error loading field metadata: <%= e.message %></div>
<% end %>
<% else %>
<p class="text-sm text-gray-500 dark:text-gray-400">Select an import job class to see required fields</p>
<% end %>
</div>
</fieldset>
<fieldset class="mt-6 border border-gray-300 rounded-md p-4 dark:border-gray-600">
<legend class="px-2 text-sm font-medium text-gray-700 dark:text-gray-300">Custom Data Fields</legend>
<div class="space-y-4" data-conference-form-target="customFields">
<% if @conference.data.present? %>
<% @conference.data.except(*@conference.required_data_fields).each do |key, value| %>
<div class="flex items-center space-x-2 custom-field-row">
<div class="flex-1">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Key</label>
<input type="text" name="custom_field_keys[]" value="<%= key %>" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white">
</div>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Value</label>
<input type="text" name="custom_field_values[]" value="<%= value %>" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white">
</div>
<div class="flex items-end">
<button type="button" data-action="click->conference-form#removeCustomField" class="mt-1 p-2 text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</button>
</div>
</div>
<% end %>
<% end %>
</div>
<template data-conference-form-target="customTemplate">
<div class="flex items-center space-x-2 custom-field-row">
<div class="flex-1">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Key</label>
<input type="text" name="custom_field_keys[]" value="KEY_PLACEHOLDER" placeholder="Enter key" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white">
</div>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Value</label>
<input type="text" name="custom_field_values[]" value="VALUE_PLACEHOLDER" placeholder="Enter value" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white">
</div>
<div class="flex items-end">
<button type="button" data-action="click->conference-form#removeCustomField" class="mt-1 p-2 text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</button>
</div>
</div>
</template>
<div class="mt-4">
<button type="button" data-action="click->conference-form#addCustomField" class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:bg-indigo-900 dark:text-indigo-200 dark:hover:bg-indigo-800">
<svg xmlns="http://www.w3.org/2000/svg" class="-ml-0.5 mr-2 h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd" />
</svg>
Add Custom Field
</button>
</div>
</fieldset>
</div>
<div class="flex justify-between pt-6">
<%= link_to "Cancel", admin_conferences_path, class: "px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600 dark:hover:bg-gray-600" %>
<%= form.submit "Update Conference", class: "inline-flex justify-center px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:bg-blue-700 dark:hover:bg-blue-800" %>
</div>
<% end %>
<div class="mt-8 pt-8 border-t border-gray-200 dark:border-gray-700">
<h2 class="text-xl font-bold mb-4 dark:text-gray-200">Danger Zone</h2>
<div class="bg-red-50 border border-red-300 rounded-md p-4 dark:bg-red-900/20 dark:border-red-800">
<h3 class="text-lg font-medium text-red-800 dark:text-red-300">Delete This Conference</h3>
<p class="mt-1 text-sm text-red-700 dark:text-red-400">Once you delete a conference, there is no going back. This will delete all associated data including sessions, speakers, and stages.</p>
<div class="mt-4">
<%= button_to "Delete Conference", admin_conference_path(slug: @conference.slug), method: :delete,
data: { confirm: "Are you sure you want to delete this conference? This action cannot be undone." },
class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 dark:bg-red-700 dark:hover:bg-red-800" %>
</div>
</div>
</div>
</div>
<div class="container mx-auto px-4 py-8">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold dark:text-gray-200">Admin: Conferences</h1>
<%= link_to "New Conference", new_admin_conference_path, class: "px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-800" %>
</div>
<div class="bg-white dark:bg-gray-800 shadow overflow-hidden rounded-lg">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Name</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Slug</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Dates</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Import Job</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
<% @conferences.each do |conference| %>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-200">
<%= link_to conference.name, conference_path(slug: conference.slug), class: "text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300" %>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400"><%= conference.slug %></td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
<%= conference.starts_at&.strftime('%Y-%m-%d') %> to <%= conference.ends_at&.strftime('%Y-%m-%d') %>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400"><%= conference.import_job_class %></td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<div class="flex space-x-2">
<%= link_to "Edit", edit_admin_conference_path(slug: conference.slug), class: "text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300" %>
<%= button_to "Delete", admin_conference_path(slug: conference.slug), method: :delete,
data: { confirm: "Are you sure you want to delete this conference? This action cannot be undone." },
class: "text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 bg-transparent border-none cursor-pointer" %>
</div>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
<div class="container mx-auto px-4 py-8">
<h1 class="text-2xl font-bold mb-6 dark:text-gray-200">Admin: New Conference</h1>
<%= form_with(model: [:admin, @conference], class: "space-y-6", data: { controller: "conference-form" }) do |form| %>
<% if @conference.errors.any? %>
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
<h2 class="font-bold"><%= pluralize(@conference.errors.count, "error") %> prohibited this conference from being saved:</h2>
<ul class="list-disc list-inside mt-2">
<% @conference.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="space-y-4">
<div>
<%= form.label :name, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.text_field :name, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white" %>
</div>
<div>
<%= form.label :slug, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.text_field :slug, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white" %>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Used in URLs. Should contain only lowercase letters, numbers, and hyphens.</p>
</div>
<div>
<%= form.label :url, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.url_field :url, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white" %>
</div>
<div>
<%= form.label :time_zone, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.time_zone_select :time_zone, nil, { include_blank: "Select Time Zone" }, { class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white" } %>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div>
<%= form.label :starts_at, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.datetime_local_field :starts_at, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white" %>
</div>
<div>
<%= form.label :ends_at, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.datetime_local_field :ends_at, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white" %>
</div>
</div>
<div>
<%= form.label :import_job_class, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.select :import_job_class,
[["Select Import Job Class", ""]] +
ConferencesController.available_import_job_classes.map { |class_name, display_name| [display_name, class_name] },
{},
{ class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white",
data: { conference_form_target: "importJobClass", action: "change->conference-form#importJobClassChanged" } } %>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Select the import job class to see required data fields</p>
</div>
<fieldset class="mt-6 border border-gray-300 rounded-md p-4 dark:border-gray-600">
<legend class="px-2 text-sm font-medium text-gray-700 dark:text-gray-300">Required Data Fields</legend>
<div class="space-y-4" data-conference-form-target="requiredFields">
<p class="text-sm text-gray-500 dark:text-gray-400">Select an import job class to see required fields</p>
</div>
</fieldset>
<fieldset class="mt-6 border border-gray-300 rounded-md p-4 dark:border-gray-600">
<legend class="px-2 text-sm font-medium text-gray-700 dark:text-gray-300">Custom Data Fields</legend>
<div class="space-y-4" data-conference-form-target="customFields">
<!-- Custom fields will be added here -->
</div>
<template data-conference-form-target="customTemplate">
<div class="flex items-center space-x-2 custom-field-row">
<div class="flex-1">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Key</label>
<input type="text" name="custom_field_keys[]" value="KEY_PLACEHOLDER" placeholder="Enter key" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white">
</div>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Value</label>
<input type="text" name="custom_field_values[]" value="VALUE_PLACEHOLDER" placeholder="Enter value" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white">
</div>
<div class="flex items-end">
<button type="button" data-action="click->conference-form#removeCustomField" class="mt-1 p-2 text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</button>
</div>
</div>
</template>
<div class="mt-4">
<button type="button" data-action="click->conference-form#addCustomField" class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:bg-indigo-900 dark:text-indigo-200 dark:hover:bg-indigo-800">
<svg xmlns="http://www.w3.org/2000/svg" class="-ml-0.5 mr-2 h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd" />
</svg>
Add Custom Field
</button>
</div>
</fieldset>
</div>
<div class="flex justify-between pt-6">
<%= link_to "Cancel", admin_conferences_path, class: "px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600 dark:hover:bg-gray-600" %>
<%= form.submit "Create Conference", class: "inline-flex justify-center px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:bg-blue-700 dark:hover:bg-blue-800" %>
</div>
<% end %>
</div>
<div class="container mx-auto px-4 py-8">
<h1 class="text-2xl font-bold mb-6 dark:text-gray-200">Admin: Edit Role: <%= @role.name %></h1>
<%= form_with(model: [:admin, @role], class: "space-y-6") do |form| %>
<% if @role.errors.any? %>
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
<h2 class="font-bold"><%= pluralize(@role.errors.count, "error") %> prohibited this role from being saved:</h2>
<ul class="list-disc list-inside mt-2">
<% @role.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="space-y-4">
<div>
<%= form.label :name, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.text_field :name, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white" %>
</div>
<div>
<%= form.label :description, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.text_area :description, rows: 3, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white" %>
</div>
<fieldset class="mt-6 border border-gray-300 rounded-md p-4 dark:border-gray-600">
<legend class="px-2 text-sm font-medium text-gray-700 dark:text-gray-300">Permissions</legend>
<div class="space-y-2 mt-2">
<% @permissions.each do |permission| %>
<div class="flex items-center">
<%= check_box_tag "role[permission_ids][]", permission.id, @role.permissions.include?(permission), id: "permission_#{permission.id}", class: "h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded dark:bg-gray-700 dark:border-gray-600" %>
<%= label_tag "permission_#{permission.id}", permission.name, class: "ml-2 block text-sm text-gray-900 dark:text-gray-300" %>
<% if permission.description.present? %>
<span class="ml-2 text-xs text-gray-500 dark:text-gray-400"><%= permission.description %></span>
<% end %>
</div>
<% end %>
</div>
</fieldset>
</div>
<div class="flex justify-between pt-6">
<%= link_to "Cancel", admin_roles_path, class: "px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600 dark:hover:bg-gray-600" %>
<%= form.submit "Update Role", class: "inline-flex justify-center px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:bg-blue-700 dark:hover:bg-blue-800" %>
</div>
<% end %>
</div>
<div class="container mx-auto px-4 py-8">
<h1 class="text-2xl font-bold mb-6 dark:text-gray-200">Admin: Roles and Permissions</h1>
<div class="bg-white dark:bg-gray-800 shadow overflow-hidden rounded-lg">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Role</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Description</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Permissions</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
<% @roles.each do |role| %>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-200">
<%= role.name %>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
<%= role.description %>
</td>
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">
<div class="flex flex-wrap gap-1">
<% role.permissions.each do |permission| %>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
<%= permission.name %>
</span>
<% end %>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<%= link_to "Edit", edit_admin_role_path(role), class: "text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300" %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
<div>
<h1 class="font-bold text-4xl">Admin::Roles#update</h1>
<p>Find me in app/views/admin/roles/update.html.erb</p>
</div>
Rails.application.routes.draw do Rails.application.routes.draw do
namespace :admin do
resources :roles, only: [ :index, :edit, :update ]
resources :conferences, param: :slug
end
devise_for :users devise_for :users
mount Crono::Engine, at: "/crono" mount Crono::Engine, at: "/crono"
mount ActionCable.server => "/cable" mount ActionCable.server => "/cable"
......
namespace :admin do
desc "Assign admin role to a user by email"
task :make_admin, [ :email ] => :environment do |t, args|
email = args[:email]
if email.blank?
puts "Error: Email is required."
puts "Usage: rake admin:make_admin[user@example.com]"
exit 1
end
user = User.find_by(email: email)
if user.nil?
puts "Error: User with email '#{email}' not found."
exit 1
end
# Get the shift_coordinator role
admin_role = Role.find_by(name: "shift_coordinator")
if admin_role.nil?
puts "Error: 'shift_coordinator' role does not exist."
exit 1
end
if user.has_role?("shift_coordinator")
puts "User '#{user.name}' already has the admin role."
else
user.roles << admin_role
puts "Successfully assigned admin role to '#{user.name}' (#{user.email})."
end
end
desc "List all admin users"
task list: :environment do
admin_role = Role.find_by(name: "shift_coordinator")
if admin_role.nil?
puts "Error: 'shift_coordinator' role does not exist."
exit 1
end
admins = admin_role.users
if admins.empty?
puts "No users with admin rights found."
else
puts "Users with admin rights:"
puts "-----------------------"
admins.each do |admin|
puts "#{admin.name} (#{admin.email || 'No email'})"
end
end
end
desc "Remove admin role from a user by email"
task :remove_admin, [ :email ] => :environment do |t, args|
email = args[:email]
if email.blank?
puts "Error: Email is required."
puts "Usage: rake admin:remove_admin[user@example.com]"
exit 1
end
user = User.find_by(email: email)
if user.nil?
puts "Error: User with email '#{email}' not found."
exit 1
end
# Get the shift_coordinator role
admin_role = Role.find_by(name: "shift_coordinator")
if admin_role.nil?
puts "Error: 'shift_coordinator' role does not exist."
exit 1
end
if !user.has_role?("shift_coordinator")
puts "User '#{user.name}' does not have the admin role."
else
user.roles.delete(admin_role)
puts "Successfully removed admin role from '#{user.name}' (#{user.email})."
end
end
end
namespace :admin do
desc "Assign events_admin role to a user by email"
task :make_events_admin, [ :email ] => :environment do |t, args|
email = args[:email]
if email.blank?
puts "Error: Email is required."
puts "Usage: rake admin:make_events_admin[user@example.com]"
exit 1
end
user = User.find_by(email: email)
if user.nil?
puts "Error: User with email '#{email}' not found."
exit 1
end
# Get the events_admin role
admin_role = Role.find_by(name: "events_admin")
if admin_role.nil?
puts "Error: 'events_admin' role does not exist."
exit 1
end
if user.has_role?("events_admin")
puts "User '#{user.name}' already has the events_admin role."
else
user.roles << admin_role
puts "Successfully assigned events_admin role to '#{user.name}' (#{user.email})."
end
end
desc "List all events admin users"
task list_events_admins: :environment do
admin_role = Role.find_by(name: "events_admin")
if admin_role.nil?
puts "Error: 'events_admin' role does not exist."
exit 1
end
admins = admin_role.users
if admins.empty?
puts "No users with events_admin rights found."
else
puts "Users with events_admin rights:"
puts "------------------------------"
admins.each do |admin|
puts "#{admin.name} (#{admin.email || 'No email'})"
end
end
end
desc "Remove events_admin role from a user by email"
task :remove_events_admin, [ :email ] => :environment do |t, args|
email = args[:email]
if email.blank?
puts "Error: Email is required."
puts "Usage: rake admin:remove_events_admin[user@example.com]"
exit 1
end
user = User.find_by(email: email)
if user.nil?
puts "Error: User with email '#{email}' not found."
exit 1
end
# Get the events_admin role
admin_role = Role.find_by(name: "events_admin")
if admin_role.nil?
puts "Error: 'events_admin' role does not exist."
exit 1
end
if !user.has_role?("events_admin")
puts "User '#{user.name}' does not have the events_admin role."
else
user.roles.delete(admin_role)
puts "Successfully removed events_admin role from '#{user.name}' (#{user.email})."
end
end
end
require "test_helper"
class Admin::RolesControllerTest < ActionDispatch::IntegrationTest
test "should get index" do
get admin_roles_index_url
assert_response :success
end
test "should get edit" do
get admin_roles_edit_url
assert_response :success
end
test "should get update" do
get admin_roles_update_url
assert_response :success
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment