fix field deletion & tone down header styling

This commit is contained in:
Andrew Brown 2025-07-05 17:38:20 -07:00
parent c94667321e
commit e0a511b50f
4 changed files with 205 additions and 122 deletions

View File

@ -1,6 +1,6 @@
class AttributeFieldsController < ContentController
before_action :authenticate_user!
before_action :set_attribute_field, only: [:update, :edit]
before_action :set_attribute_field, only: [:update, :edit, :destroy]
def create
if initialize_object.save!
@ -19,12 +19,40 @@ class AttributeFieldsController < ContentController
end
def destroy
# Delete this field as usual -- sets @content
super
unless @attribute_field.deletable_by?(current_user)
respond_to do |format|
format.html { redirect_back fallback_location: root_path, alert: "You don't have permission to delete that!" }
format.json { render json: { success: false, error: "You don't have permission to delete that!" }, status: :forbidden }
end
return
end
# Store references before deletion
field_label = @attribute_field.label
related_category = @attribute_field.attribute_category
# Delete the field
@attribute_field.destroy
# If the related category is now empty, delete it as well
related_category = @content.attribute_category
related_category.destroy if related_category.attribute_fields.empty?
if related_category.attribute_fields.empty?
related_category.destroy
end
# Respond with success
respond_to do |format|
format.html {
redirect_to attribute_customization_tailwind_path(content_type: related_category.entity_type),
notice: "#{field_label} field deleted successfully"
}
format.json {
render json: {
success: true,
message: "#{field_label} field deleted successfully",
deleted_field_id: params[:id]
}, status: :ok
}
end
end
def edit

View File

@ -1,5 +1,94 @@
// Template Editor JavaScript
// Field Deletion Component function for Alpine.js (define before DOM ready)
window.fieldDeletionComponent = function() {
return {
showConfirmation: false,
deleting: false,
deleteField() {
this.deleting = true;
// Get field information from the current context
const fieldConfigContainer = document.getElementById('field-config-container');
const fieldForm = fieldConfigContainer.querySelector('form[action*="/attribute_fields/"]');
if (!fieldForm) {
console.error('Field form not found');
showNotification('Unable to delete field - form not found', 'error');
this.deleting = false;
return;
}
// Extract field ID from form action URL
const fieldIdMatch = fieldForm.action.match(/\/attribute_fields\/(\d+)/);
if (!fieldIdMatch) {
console.error('Field ID not found in form action');
showNotification('Unable to delete field - ID not found', 'error');
this.deleting = false;
return;
}
const fieldId = fieldIdMatch[1];
// Perform deletion via AJAX
fetch(`/plan/attribute_fields/${fieldId}`, {
method: 'DELETE',
headers: {
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
})
.then(data => {
console.log('Field deleted successfully:', data);
// Remove the field from the UI
const fieldItem = document.querySelector(`[data-field-id="${fieldId}"]`);
if (fieldItem) {
// Update field count in category header before removing
const categoryId = fieldItem.closest('.fields-container').dataset.categoryId;
fieldItem.remove();
updateCategoryFieldCount(categoryId);
}
// Update General Settings counts
updateGeneralSettingsCounts();
// Close the configuration panel
const alpineElement = document.querySelector('.attributes-editor');
if (alpineElement && alpineElement._x_dataStack) {
const alpine = alpineElement._x_dataStack[0];
if (alpine) {
alpine.selectedField = null;
alpine.configuring = false;
}
}
// Show success notification
if (data.message) {
showNotification(data.message, 'success');
} else {
showNotification('Field deleted successfully', 'success');
}
this.deleting = false;
})
.catch(error => {
console.error('Failed to delete field:', error);
showNotification('Failed to delete field', 'error');
this.deleting = false;
});
}
};
};
// Template Reset Component function for Alpine.js (define before DOM ready)
window.templateResetComponent = function() {
return {

View File

@ -429,18 +429,56 @@
<i class="material-icons text-red-600 text-sm mr-1">warning</i>
Danger Zone
</h6>
<div class="bg-red-50 p-4 rounded-md border border-red-200">
<p class="text-sm text-red-700 mb-3">
Deleting a field will permanently remove it and all its data from all <%= content_type.pluralize.downcase %>.
</p>
<div class="bg-red-50 p-4 rounded-md border border-red-200" x-data="fieldDeletionComponent()">
<div x-show="!showConfirmation">
<p class="text-sm text-red-700 mb-3">
Deleting a field will permanently remove it and all its data from all <%= content_type.pluralize.downcase %>.
</p>
<button @click="showConfirmation = true"
class="w-full px-3 py-1.5 border border-transparent rounded-md text-sm font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-colors">
Delete Field
</button>
</div>
<%= link_to "Delete Field",
field,
class: 'w-full inline-block text-center px-3 py-1.5 border border-transparent rounded-md text-sm font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500',
method: :delete,
data: {
confirm: "Are you sure? This will delete this field AND all of its answers to EVERY page you've created. This action cannot be undone!"
} %>
<!-- Confirmation Step -->
<div x-show="showConfirmation"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 transform scale-95"
x-transition:enter-end="opacity-100 transform scale-100">
<div class="bg-red-100 rounded-md p-3 border border-red-300 mb-4">
<h4 class="font-medium text-red-900 mb-2 flex items-center">
<i class="material-icons text-red-600 mr-2">warning</i>
Confirm Field Deletion
</h4>
<p class="text-sm text-red-800 mb-3">
This will permanently delete "<strong><%= field.label %></strong>" and all answers you've written to this field across all of your <%= content_type.pluralize.downcase %> pages.
</p>
<p class="text-xs text-red-700 font-medium">
This action cannot be undone!
</p>
</div>
<div class="flex justify-end space-x-2">
<button @click="showConfirmation = false"
class="px-3 py-1.5 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none">
Cancel
</button>
<button @click="deleteField()"
:disabled="deleting"
:class="deleting ? 'opacity-50 cursor-not-allowed' : ''"
class="px-3 py-1.5 border border-transparent rounded-md text-sm font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-colors">
<span x-show="!deleting">Yes, Delete Field</span>
<span x-show="deleting" class="flex items-center">
<svg class="animate-spin -ml-1 mr-2 h-3 w-3 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Deleting...
</span>
</button>
</div>
</div>
</div>
</div>
<% end %>

View File

@ -34,69 +34,37 @@
<div :class="{ 'hidden md:block': activePanel === 'config' }"
class="flex-1 min-w-0">
<!-- Beautiful Hero Header -->
<div class="template-hero-header relative overflow-hidden rounded-xl shadow-xl mb-8 bg-white">
<!-- Background Image with Overlay -->
<!-- Streamlined Header -->
<div class="template-header relative overflow-hidden rounded-lg shadow-md mb-6 bg-white">
<!-- Background with Frosted Glass Effect -->
<div class="absolute inset-0">
<%= image_tag "card-headers/#{@content_type.downcase.pluralize}.jpg",
class: "hero-bg-image w-full h-full object-cover",
alt: "#{@content_type.titleize} background" %>
<div class="absolute inset-0 bg-gradient-to-r from-black/70 via-black/50 to-black/30"></div>
<div class="absolute inset-0" style="background: linear-gradient(135deg, <%= @content_type_class.hex_color %>cc 0%, <%= @content_type_class.hex_color %>66 50%, transparent 100%);"></div>
<!-- Frosted glass overlay for better text readability -->
<div class="absolute inset-0 bg-black/40 backdrop-blur-md"></div>
<div class="absolute inset-0" style="background: linear-gradient(135deg, <%= @content_type_class.hex_color %>88 0%, <%= @content_type_class.hex_color %>44 50%, transparent 100%);"></div>
</div>
<!-- Content -->
<div class="relative z-10 px-8 py-12">
<!-- Icon and Title -->
<div class="glass-panel rounded-xl p-6 max-w-4xl">
<div class="flex items-center mb-4">
<div class="w-16 h-16 rounded-xl bg-white/20 backdrop-blur-sm border border-white/30 flex items-center justify-center mr-4 shadow-lg">
<i class="material-icons text-2xl" style="color: <%= @content_type_class.hex_color %>;"><%= @content_type_class.icon %></i>
<div class="relative z-10 px-6 py-5">
<div class="flex items-start justify-between">
<!-- Icon and Title -->
<div class="flex items-center">
<div class="w-12 h-12 rounded-lg flex items-center justify-center mr-4 content-type-icon-bg">
<i class="material-icons text-white text-xl"><%= @content_type_class.icon %></i>
</div>
<div>
<h1 class="text-3xl font-bold text-white mb-1">
<h1 class="text-2xl font-semibold text-white leading-tight mb-1">
<%= @content_type.titleize %> Template Editor
</h1>
<p class="text-white/90 text-lg font-medium">
Design your perfect <%= @content_type.titleize.downcase %> structure
<p class="text-white text-sm leading-relaxed max-w-2xl">
Edit the template used for your <%= @content_type.titleize.downcase %> pages. Adding or removing a field here will modify all of your already-created <%= @content_type.titleize.downcase %> pages also.
</p>
</div>
</div>
<!-- Description -->
<div class="max-w-2xl">
<p class="text-white text-base leading-relaxed mb-6">
This template will be used for all of your <%= @content_type.titleize.downcase %> pages.
You can customize categories and fields to fit your needs; adding or removing categories or
fields on this page will also add or remove them from all of your existing <%= @content_type.titleize.downcase %> pages.
</p>
<!-- Interactive Hints -->
<div class="flex items-center space-x-6 text-sm text-white/75">
<div class="interactive-hint flex items-center">
<i class="material-icons text-white/60 mr-2 text-lg transition-all duration-300">drag_indicator</i>
<span>Drag to reorder</span>
</div>
<div class="interactive-hint flex items-center">
<i class="material-icons text-white/60 mr-2 text-lg transition-all duration-300">tune</i>
<span>Click to configure</span>
</div>
<div class="interactive-hint flex items-center">
<i class="material-icons text-white/60 mr-2 text-lg transition-all duration-300">add_circle</i>
<span>Add new elements</span>
</div>
</div>
</div>
</div>
</div>
<!-- Decorative Elements -->
<div class="floating-decoration absolute bottom-0 right-0 w-64 h-64 opacity-10">
<div class="w-full h-full rounded-full border-4 border-white/30 transform rotate-12"></div>
</div>
<div class="floating-decoration absolute top-4 right-4 w-32 h-32 opacity-5">
<div class="w-full h-full rounded-full border-2 border-white/50"></div>
</div>
</div>
<!-- Categories Container -->
@ -217,91 +185,51 @@
<style>
/* Beautiful Hero Header Styles */
.template-hero-header {
/* Streamlined Header Styles */
.template-header {
position: relative;
background: linear-gradient(135deg, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.3) 100%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
transition: all 0.2s ease;
}
.template-hero-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, transparent 50%);
pointer-events: none;
.template-header:hover {
shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
/* Parallax effect for background image */
/* Subtle background image effect */
.hero-bg-image {
transform: scale(1.1);
transition: transform 0.8s ease-out;
transition: opacity 0.3s ease;
}
.template-hero-header:hover .hero-bg-image {
transform: scale(1.05);
.template-header:hover .hero-bg-image {
opacity: 0.8;
}
/* Animated floating elements */
@keyframes float {
0%, 100% { transform: translateY(0px) rotate(0deg); }
50% { transform: translateY(-10px) rotate(1deg); }
}
.floating-decoration {
animation: float 6s ease-in-out infinite;
}
.floating-decoration:nth-child(2) {
animation-delay: -2s;
animation-duration: 8s;
}
/* Enhanced backdrop effects */
.backdrop-blur-enhanced {
backdrop-filter: blur(12px) saturate(180%);
-webkit-backdrop-filter: blur(12px) saturate(180%);
background: rgba(255, 255, 255, 0.1);
/* Content type icon styling */
.content-type-icon-bg {
background: linear-gradient(135deg, var(--content-type-color) 0%, color-mix(in srgb, var(--content-type-color) 80%, white) 100%);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
}
/* Glassmorphism effect for stats */
.glass-panel {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
.content-type-icon-bg:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
/* Interactive hint animations */
/* Interactive hint styling */
.interactive-hint {
transition: all 0.3s ease;
transition: all 0.2s ease;
cursor: default;
}
.interactive-hint:hover {
transform: translateY(-2px);
color: rgba(255, 255, 255, 1);
}
.interactive-hint:hover i {
color: rgba(255, 255, 255, 0.9);
transform: scale(1.1);
}
/* Stats counter animation */
.stats-counter {
transition: all 0.3s ease;
}
.stats-counter:hover {
transform: scale(1.05);
background: rgba(255, 255, 255, 0.3);
}
/* jQuery UI Sortable placeholder styles */