Fix admin route access and backend configuration
- Added /admin redirect to login page in nginx config - Fixed backend server.js route ordering for proper admin handling - Updated authentication middleware and routes - Added user management routes - Configured PostgreSQL integration - Updated environment configuration
This commit is contained in:
172
Views/AdminHomepage/Create.cshtml
Executable file
172
Views/AdminHomepage/Create.cshtml
Executable file
@@ -0,0 +1,172 @@
|
||||
@model SkyArtShop.Models.HomepageSection
|
||||
@{
|
||||
ViewData["Title"] = "Create Homepage Section";
|
||||
Layout = "_AdminLayout";
|
||||
}
|
||||
|
||||
<style>
|
||||
.form-check-input[type="checkbox"] {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
cursor: pointer;
|
||||
border: 2px solid #dee2e6;
|
||||
border-radius: 4px;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
background-color: white;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-check-input[type="checkbox"]:checked {
|
||||
background-color: #28a745;
|
||||
border-color: #28a745;
|
||||
}
|
||||
|
||||
.form-check-input[type="checkbox"]:checked::after {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.form-check-input[type="checkbox"]:hover {
|
||||
border-color: #28a745;
|
||||
box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="mb-4">
|
||||
<a href="/admin/homepage" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Back to Homepage Editor
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h4 class="mb-0"><i class="bi bi-plus-circle"></i> Create New Homepage Section</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="/admin/homepage/section/create" enctype="multipart/form-data">
|
||||
@Html.AntiForgeryToken()
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="SectionType" class="form-label">Section Type <span class="text-danger">*</span></label>
|
||||
<select id="SectionType" name="SectionType" class="form-select" required>
|
||||
<option value="">-- Select Section Type --</option>
|
||||
<option value="hero">Hero Section</option>
|
||||
<option value="inspiration">Inspiration Section</option>
|
||||
<option value="collection">Collection Section</option>
|
||||
<option value="promotion">Promotion Section</option>
|
||||
<option value="custom">Custom Section</option>
|
||||
</select>
|
||||
<small class="text-muted">Choose the type of content section you want to add</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Status</label>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="IsActive" name="IsActive" value="true" checked>
|
||||
<label class="form-check-label" for="IsActive">Active (visible on homepage)</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="Title" class="form-label">Section Title <span class="text-danger">*</span></label>
|
||||
<input type="text" id="Title" name="Title" class="form-control" placeholder="Enter section title" required />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="Subtitle" class="form-label">Subtitle</label>
|
||||
<input type="text" id="Subtitle" name="Subtitle" class="form-control" placeholder="Enter subtitle (optional)" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="Content" class="form-label">Content</label>
|
||||
<textarea id="Content" name="Content" class="form-control" rows="6" placeholder="Enter your content here..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="ButtonText" class="form-label">Button Text</label>
|
||||
<input type="text" id="ButtonText" name="ButtonText" class="form-control" placeholder="e.g., Shop Now, Learn More" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="ButtonUrl" class="form-label">Button URL</label>
|
||||
<input type="text" id="ButtonUrl" name="ButtonUrl" class="form-control" placeholder="e.g., /Shop, /Contact" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="imageFile" class="form-label">Section Image</label>
|
||||
<input type="file" id="imageFile" name="imageFile" class="form-control" accept="image/*" />
|
||||
<small class="text-muted">Supported formats: JPG, PNG, GIF (max 5MB)</small>
|
||||
</div>
|
||||
|
||||
<hr class="my-4" />
|
||||
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> <strong>Note:</strong> This section will be added to the end of your homepage. You can reorder it by dragging on the main editor page.
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="/admin/homepage" class="btn btn-secondary">Cancel</a>
|
||||
<button type="submit" class="btn btn-success btn-lg">
|
||||
<i class="bi bi-plus-circle"></i> Create Section
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts
|
||||
{
|
||||
<script>
|
||||
let contentEditor;
|
||||
ClassicEditor
|
||||
.create(document.querySelector('#Content'), {
|
||||
toolbar: [
|
||||
'heading', '|',
|
||||
'bold', 'italic', '|',
|
||||
'link', 'bulletedList', 'numberedList', '|',
|
||||
'indent', 'outdent', '|',
|
||||
'blockQuote', 'insertTable', '|',
|
||||
'undo', 'redo'
|
||||
],
|
||||
heading: {
|
||||
options: [
|
||||
{ model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
|
||||
{ model: 'heading1', view: 'h1', title: 'Heading 1', class: 'ck-heading_heading1' },
|
||||
{ model: 'heading2', view: 'h2', title: 'Heading 2', class: 'ck-heading_heading2' },
|
||||
{ model: 'heading3', view: 'h3', title: 'Heading 3', class: 'ck-heading_heading3' }
|
||||
]
|
||||
}
|
||||
})
|
||||
.then(editor => {
|
||||
contentEditor = editor;
|
||||
document.querySelector('form').addEventListener('submit', function(e) {
|
||||
document.querySelector('#Content').value = contentEditor.getData();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('CKEditor initialization error:', error);
|
||||
});
|
||||
</script>
|
||||
}
|
||||
233
Views/AdminHomepage/Edit.cshtml
Executable file
233
Views/AdminHomepage/Edit.cshtml
Executable file
@@ -0,0 +1,233 @@
|
||||
@model SkyArtShop.Models.HomepageSection
|
||||
@{
|
||||
ViewData["Title"] = "Edit Homepage Section";
|
||||
Layout = "_AdminLayout";
|
||||
}
|
||||
|
||||
<style>
|
||||
.form-check-input[type="checkbox"] {
|
||||
width: 22px !important;
|
||||
height: 22px !important;
|
||||
cursor: pointer !important;
|
||||
border: 2px solid #dee2e6 !important;
|
||||
border-radius: 4px !important;
|
||||
appearance: none !important;
|
||||
-webkit-appearance: none !important;
|
||||
-moz-appearance: none !important;
|
||||
background-color: white !important;
|
||||
background-image: none !important;
|
||||
transition: all 0.2s ease !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.form-check-input[type="checkbox"]:checked {
|
||||
background-color: #28a745 !important;
|
||||
border-color: #28a745;
|
||||
}
|
||||
|
||||
.form-check-input[type="checkbox"]:checked::after {
|
||||
content: '✓';
|
||||
position: absolute !important;
|
||||
top: 50% !important;
|
||||
left: 50% !important;
|
||||
transform: translate(-50%, -50%) !important;
|
||||
color: white !important;
|
||||
font-size: 16px !important;
|
||||
font-weight: bold !important;
|
||||
line-height: 1 !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.form-check-input[type="checkbox"]:hover {
|
||||
border-color: #28a745;
|
||||
box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="mb-4">
|
||||
<a href="/admin/homepage" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Back to Homepage Editor
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h4 class="mb-0">Edit Section: @Model.Title</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="/admin/homepage/section/update" enctype="multipart/form-data">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="Id" value="@Model.Id" />
|
||||
<input type="hidden" name="DisplayOrder" value="@Model.DisplayOrder" />
|
||||
<input type="hidden" name="CreatedAt" value="@Model.CreatedAt" />
|
||||
<input type="hidden" id="ImageUrl" name="ImageUrl" value="@Model.ImageUrl" />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="SectionType" class="form-label">Section Type <span class="text-danger">*</span></label>
|
||||
<select id="SectionType" name="SectionType" class="form-select" required>
|
||||
<option value="hero" selected="@(Model.SectionType == "hero")">Hero Section</option>
|
||||
<option value="inspiration" selected="@(Model.SectionType == "inspiration")">Inspiration Section</option>
|
||||
<option value="collection" selected="@(Model.SectionType == "collection")">Collection Section</option>
|
||||
<option value="promotion" selected="@(Model.SectionType == "promotion")">Promotion Section</option>
|
||||
<option value="custom" selected="@(Model.SectionType == "custom")">Custom Section</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Status</label>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="IsActive" name="IsActive" value="true" checked="@Model.IsActive">
|
||||
<label class="form-check-label" for="IsActive">Active (visible on homepage)</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="Title" class="form-label">Section Title <span class="text-danger">*</span></label>
|
||||
<input type="text" id="Title" name="Title" class="form-control" value="@Model.Title" required />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="Subtitle" class="form-label">Subtitle</label>
|
||||
<input type="text" id="Subtitle" name="Subtitle" class="form-control" value="@Model.Subtitle" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="Content" class="form-label">Content</label>
|
||||
<textarea id="Content" name="Content" class="form-control" rows="6">@Model.Content</textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="ButtonText" class="form-label">Button Text</label>
|
||||
<input type="text" id="ButtonText" name="ButtonText" class="form-control" value="@Model.ButtonText" placeholder="e.g., Shop Now, Learn More" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="ButtonUrl" class="form-label">Button URL</label>
|
||||
<input type="text" id="ButtonUrl" name="ButtonUrl" class="form-control" value="@Model.ButtonUrl" placeholder="e.g., /Shop, /Contact" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="imageFile" class="form-label">Section Image</label>
|
||||
<div class="mb-2" id="currentImagePreview" style="@(string.IsNullOrEmpty(Model.ImageUrl) ? "display: none;" : "")">
|
||||
<img src="@(Model.ImageUrl ?? "")" alt="Current image" style="max-width: 300px; max-height: 200px; border: 1px solid #ddd; border-radius: 4px !important;" id="sectionImagePreview" />
|
||||
<p class="text-muted small mt-1">Current image</p>
|
||||
</div>
|
||||
<input type="hidden" id="SelectedImageUrl" name="SelectedImageUrl" value="" />
|
||||
<button type="button" class="btn btn-primary" onclick="openImagePicker(handleSectionImageSelection, 'single')">
|
||||
<i class="bi bi-images"></i> Select/Upload Image
|
||||
</button>
|
||||
<small class="text-muted d-block mt-1">Select from library or upload new image</small>
|
||||
</div>
|
||||
|
||||
<hr class="my-4" />
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="/admin/homepage" class="btn btn-secondary">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary btn-lg">
|
||||
<i class="bi bi-check-circle"></i> Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts
|
||||
{
|
||||
<script>
|
||||
// Handle Section Image Selection
|
||||
function handleSectionImageSelection(selectedUrls) {
|
||||
if (!selectedUrls || selectedUrls.length === 0) {
|
||||
alert('No images selected. Please try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const imageUrl = selectedUrls[0];
|
||||
const selectedInput = document.getElementById('SelectedImageUrl');
|
||||
const previewImg = document.getElementById('sectionImagePreview');
|
||||
const previewDiv = document.getElementById('currentImagePreview');
|
||||
|
||||
if (!selectedInput || !previewImg || !previewDiv) {
|
||||
console.error('Required DOM elements not found');
|
||||
return;
|
||||
}
|
||||
|
||||
selectedInput.value = imageUrl;
|
||||
previewImg.src = imageUrl;
|
||||
previewDiv.style.display = 'block';
|
||||
} catch (error) {
|
||||
console.error('Error in handleSectionImageSelection:', error);
|
||||
alert('Error setting image: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure checkbox value is properly set before form submission
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.querySelector('form');
|
||||
const isActiveCheckbox = document.getElementById('IsActive');
|
||||
|
||||
if (form && isActiveCheckbox) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
// Set the hidden input value based on checkbox state
|
||||
if (isActiveCheckbox.checked) {
|
||||
isActiveCheckbox.value = 'true';
|
||||
} else {
|
||||
// Add a hidden input to ensure false is submitted
|
||||
const hiddenFalse = document.createElement('input');
|
||||
hiddenFalse.type = 'hidden';
|
||||
hiddenFalse.name = 'IsActive';
|
||||
hiddenFalse.value = 'false';
|
||||
form.appendChild(hiddenFalse);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let contentEditor;
|
||||
ClassicEditor
|
||||
.create(document.querySelector('#Content'), {
|
||||
toolbar: [
|
||||
'heading', '|',
|
||||
'bold', 'italic', '|',
|
||||
'link', 'bulletedList', 'numberedList', '|',
|
||||
'indent', 'outdent', '|',
|
||||
'blockQuote', 'insertTable', '|',
|
||||
'undo', 'redo'
|
||||
],
|
||||
heading: {
|
||||
options: [
|
||||
{ model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
|
||||
{ model: 'heading1', view: 'h1', title: 'Heading 1', class: 'ck-heading_heading1' },
|
||||
{ model: 'heading2', view: 'h2', title: 'Heading 2', class: 'ck-heading_heading2' },
|
||||
{ model: 'heading3', view: 'h3', title: 'Heading 3', class: 'ck-heading_heading3' }
|
||||
]
|
||||
}
|
||||
})
|
||||
.then(editor => {
|
||||
contentEditor = editor;
|
||||
document.querySelector('form').addEventListener('submit', function(e) {
|
||||
// Sync CKEditor content to textarea before submit
|
||||
const contentTextarea = document.querySelector('#Content');
|
||||
if (contentTextarea && contentEditor) {
|
||||
contentTextarea.value = contentEditor.getData();
|
||||
console.log('Form submitted with content:', contentTextarea.value.substring(0, 100));
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('CKEditor initialization error:', error);
|
||||
});
|
||||
</script>
|
||||
}
|
||||
252
Views/AdminHomepage/Index.cshtml
Executable file
252
Views/AdminHomepage/Index.cshtml
Executable file
@@ -0,0 +1,252 @@
|
||||
@model List<SkyArtShop.Models.HomepageSection>
|
||||
@{
|
||||
ViewData["Title"] = "Homepage Editor";
|
||||
Layout = "_AdminLayout";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Homepage Editor</h2>
|
||||
<a href="/admin/homepage/section/create" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> Add New Section
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if (TempData["SuccessMessage"] != null)
|
||||
{
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
@TempData["SuccessMessage"]
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Footer Editor -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-footer"></i> Footer Text</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="/admin/homepage/footer/update">
|
||||
@Html.AntiForgeryToken()
|
||||
<div class="mb-3">
|
||||
<textarea id="footerText" name="footerText" class="form-control" rows="3">@ViewBag.Settings.FooterText</textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="bi bi-check-circle"></i> Save Footer
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Homepage Sections -->
|
||||
<div class="card">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-layout-text-window-reverse"></i> Homepage Sections</h5>
|
||||
<small>Drag and drop to reorder sections</small>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (Model != null && Model.Any())
|
||||
{
|
||||
<div id="sortable-sections" class="list-group">
|
||||
@foreach (var sect in Model)
|
||||
{
|
||||
<div class="list-group-item section-item" data-id="@sect.Id">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-1 text-center drag-handle" style="cursor: grab;">
|
||||
<i class="bi bi-grip-vertical" style="font-size: 1.5rem; color: #6c757d;"></i>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<span class="badge bg-secondary">@sect.SectionType</span>
|
||||
@if (!sect.IsActive)
|
||||
{
|
||||
<span class="badge bg-warning ms-1">Inactive</span>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<strong>@sect.Title</strong>
|
||||
@if (!string.IsNullOrEmpty(sect.Subtitle))
|
||||
{
|
||||
<br /><small class="text-muted">@sect.Subtitle</small>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-2 text-center">
|
||||
<small class="text-muted">Order: @sect.DisplayOrder</small>
|
||||
</div>
|
||||
<div class="col-md-3 text-end">
|
||||
<div class="d-flex gap-2 justify-content-end">
|
||||
<a href="/admin/homepage/section/@sect.Id" class="btn btn-sm btn-outline-primary" title="Edit Section">
|
||||
<i class="bi bi-pencil"></i> Edit
|
||||
</a>
|
||||
<form method="post" action="/admin/homepage/section/toggle/@sect.Id" class="d-inline">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="btn btn-sm btn-outline-@(sect.IsActive ? "warning" : "success")" title="@(sect.IsActive ? "Deactivate" : "Activate")">
|
||||
<i class="bi bi-@(sect.IsActive ? "eye-slash" : "eye")"></i>
|
||||
</button>
|
||||
</form>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteSection('@sect.Id')" title="Delete Section">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> No sections found. Click "Add New Section" to create your first homepage section.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preview Button -->
|
||||
<div class="mt-4">
|
||||
<a href="/" target="_blank" class="btn btn-secondary btn-lg">
|
||||
<i class="bi bi-eye"></i> Preview Homepage
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@section Scripts
|
||||
{
|
||||
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize CKEditor for Footer (if it exists)
|
||||
const footerTextarea = document.querySelector('#footerText');
|
||||
if (footerTextarea && typeof ClassicEditor !== 'undefined') {
|
||||
let footerEditor;
|
||||
ClassicEditor
|
||||
.create(footerTextarea, {
|
||||
toolbar: ['bold', 'italic', 'link']
|
||||
})
|
||||
.then(editor => {
|
||||
footerEditor = editor;
|
||||
const footerForm = footerTextarea.closest('form');
|
||||
if (footerForm) {
|
||||
footerForm.addEventListener('submit', function(e) {
|
||||
footerTextarea.value = footerEditor.getData();
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('CKEditor initialization error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize Sortable for drag & drop
|
||||
const sortableList = document.getElementById('sortable-sections');
|
||||
if (sortableList) {
|
||||
const sortable = Sortable.create(sortableList, {
|
||||
animation: 200,
|
||||
ghostClass: 'sortable-ghost',
|
||||
dragClass: 'sortable-drag',
|
||||
handle: '.drag-handle',
|
||||
draggable: '.section-item',
|
||||
onStart: function(evt) {
|
||||
evt.item.style.cursor = 'grabbing';
|
||||
},
|
||||
onEnd: function (evt) {
|
||||
evt.item.style.cursor = '';
|
||||
const sectionIds = Array.from(sortableList.children).map(item => item.getAttribute('data-id'));
|
||||
|
||||
fetch('/admin/homepage/section/reorder', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
|
||||
},
|
||||
body: JSON.stringify(sectionIds)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Update display order numbers
|
||||
sortableList.querySelectorAll('.section-item').forEach((item, index) => {
|
||||
item.querySelector('.col-md-2.text-center small').textContent = 'Order: ' + index;
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error updating section order:', error);
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('sortable-sections element not found');
|
||||
}
|
||||
});
|
||||
|
||||
function deleteSection(id) {
|
||||
if (confirm('Are you sure you want to delete this section?')) {
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '/admin/homepage/section/delete/' + id;
|
||||
|
||||
const token = document.querySelector('input[name="__RequestVerificationToken"]').cloneNode();
|
||||
form.appendChild(token);
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.section-item {
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 12px;
|
||||
border-left: 4px solid #6c757d;
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.section-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
border-left-color: #0d6efd;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
.drag-handle {
|
||||
transition: all 0.2s ease;
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
.drag-handle:hover {
|
||||
transform: scale(1.1);
|
||||
color: #0d6efd !important;
|
||||
cursor: grab;
|
||||
}
|
||||
.drag-handle:active {
|
||||
cursor: grabbing !important;
|
||||
}
|
||||
#sortable-sections {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sortable-ghost {
|
||||
opacity: 0.5;
|
||||
background: #e3f2fd !important;
|
||||
border: 2px dashed #0d6efd !important;
|
||||
}
|
||||
.sortable-drag {
|
||||
opacity: 0.8;
|
||||
cursor: grabbing !important;
|
||||
transform: rotate(2deg);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.3) !important;
|
||||
}
|
||||
.sortable-fallback {
|
||||
opacity: 0.8;
|
||||
background: white !important;
|
||||
box-shadow: 0 5px 20px rgba(0,0,0,0.3) !important;
|
||||
}
|
||||
.btn-group .btn, .d-flex .btn {
|
||||
min-width: 75px;
|
||||
}
|
||||
.list-group-item {
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
Reference in New Issue
Block a user