mirror of
https://github.com/indentlabs/notebook.git
synced 2025-10-26 11:19:22 +00:00
fix field deletion & tone down header styling
This commit is contained in:
parent
c94667321e
commit
e0a511b50f
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 %>
|
||||
|
||||
@ -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 */
|
||||
|
||||
Loading…
Reference in New Issue
Block a user