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:
91
Sky_Art_shop/Views/AdminPages/Create.cshtml
Normal file
91
Sky_Art_shop/Views/AdminPages/Create.cshtml
Normal file
@@ -0,0 +1,91 @@
|
||||
@model SkyArtShop.Models.Page
|
||||
@{
|
||||
Layout = "~/Views/Shared/_AdminLayout.cshtml";
|
||||
ViewData["Title"] = "Create Page";
|
||||
}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
<div asp-validation-summary="All" class="text-danger mb-3"></div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Page Name</label>
|
||||
<input class="form-control" asp-for="PageName" required />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Title</label>
|
||||
<input class="form-control" asp-for="Title" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Subtitle</label>
|
||||
<input class="form-control" asp-for="Subtitle" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Content</label>
|
||||
<textarea class="form-control" asp-for="Content" id="pageContent" rows="15"></textarea>
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input asp-for="IsActive" class="form-check-input" type="checkbox" />
|
||||
<label class="form-check-label">Active</label>
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">Save Page</button>
|
||||
<a class="btn btn-secondary" href="/admin/pages">Cancel</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script src="https://cdn.ckeditor.com/ckeditor5/40.1.0/classic/ckeditor.js"></script>
|
||||
<script>
|
||||
ClassicEditor
|
||||
.create(document.querySelector('#pageContent'), {
|
||||
toolbar: {
|
||||
items: [
|
||||
'heading', '|',
|
||||
'bold', 'italic', 'underline', 'strikethrough', '|',
|
||||
'link', 'blockQuote', '|',
|
||||
'bulletedList', 'numberedList', '|',
|
||||
'outdent', 'indent', '|',
|
||||
'alignment', '|',
|
||||
'insertTable', '|',
|
||||
'fontSize', 'fontColor', 'fontBackgroundColor', '|',
|
||||
'removeFormat', '|',
|
||||
'undo', 'redo', '|',
|
||||
'sourceEditing'
|
||||
],
|
||||
shouldNotGroupWhenFull: true
|
||||
},
|
||||
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' },
|
||||
{ model: 'heading4', view: 'h4', title: 'Heading 4', class: 'ck-heading_heading4' }
|
||||
]
|
||||
},
|
||||
fontSize: {
|
||||
options: [
|
||||
'small',
|
||||
'default',
|
||||
'big'
|
||||
]
|
||||
},
|
||||
table: {
|
||||
contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells']
|
||||
},
|
||||
htmlSupport: {
|
||||
allow: [
|
||||
{
|
||||
name: /.*/,
|
||||
attributes: true,
|
||||
classes: true,
|
||||
styles: true
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
</script>
|
||||
}
|
||||
447
Sky_Art_shop/Views/AdminPages/Edit.cshtml
Normal file
447
Sky_Art_shop/Views/AdminPages/Edit.cshtml
Normal file
@@ -0,0 +1,447 @@
|
||||
@model SkyArtShop.Models.Page
|
||||
@{
|
||||
Layout = "~/Views/Shared/_AdminLayout.cshtml";
|
||||
ViewData["Title"] = "Edit Page";
|
||||
}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="post" enctype="multipart/form-data" id="pageEditForm">
|
||||
<div asp-validation-summary="All" class="text-danger mb-3"></div>
|
||||
|
||||
<ul class="nav nav-tabs mb-4" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-bs-toggle="tab" href="#basic-tab">Basic Info</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" href="#gallery-tab">Image Gallery</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" href="#team-tab">Team Members</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<!-- Basic Info Tab -->
|
||||
<div class="tab-pane fade show active" id="basic-tab">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Page Name</label>
|
||||
<input class="form-control" asp-for="PageName" required />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Title</label>
|
||||
<input class="form-control" asp-for="Title" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Subtitle</label>
|
||||
<input class="form-control" asp-for="Subtitle" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Content</label>
|
||||
<textarea class="form-control" asp-for="Content" id="pageContent" rows="15"></textarea>
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input asp-for="IsActive" class="form-check-input" type="checkbox" />
|
||||
<label class="form-check-label">Active</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image Gallery Tab -->
|
||||
<div class="tab-pane fade" id="gallery-tab">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Image Gallery (Right Sidebar)</label>
|
||||
<p class="text-muted small">These images will appear on the right side of the About page</p>
|
||||
<div class="input-group mb-2">
|
||||
<input type="file" class="form-control" id="galleryImageUpload" accept="image/*" multiple />
|
||||
<button type="button" class="btn btn-primary" onclick="uploadGalleryImages()">
|
||||
<i class="bi bi-cloud-upload"></i> Upload Images
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-muted">You can select multiple images at once</small>
|
||||
</div>
|
||||
<div id="galleryImagesContainer" class="row g-3">
|
||||
@if (Model.ImageGallery != null && Model.ImageGallery.Any())
|
||||
{
|
||||
for (int i = 0; i < Model.ImageGallery.Count; i++)
|
||||
{
|
||||
<div class="col-md-4 gallery-image-item">
|
||||
<div class="card">
|
||||
<img src="@Model.ImageGallery[i]" class="card-img-top" style="height: 150px; object-fit: cover;" />
|
||||
<div class="card-body p-2">
|
||||
<input type="hidden" name="ImageGallery[@i]" value="@Model.ImageGallery[i]" />
|
||||
<button type="button" class="btn btn-sm btn-danger w-100" onclick="removeGalleryImage(this)">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Team Members Tab -->
|
||||
<div class="tab-pane fade" id="team-tab">
|
||||
<div class="mb-3">
|
||||
<button type="button" class="btn btn-primary" onclick="addTeamMember()">
|
||||
<i class="bi bi-plus-circle"></i> Add Team Member
|
||||
</button>
|
||||
</div>
|
||||
<div id="teamMembersContainer">
|
||||
@if (Model.TeamMembers != null && Model.TeamMembers.Any())
|
||||
{
|
||||
for (int i = 0; i < Model.TeamMembers.Count; i++)
|
||||
{
|
||||
<div class="card mb-3 team-member-card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">Team Member #@(i + 1)</h6>
|
||||
<button type="button" class="btn btn-sm btn-danger" onclick="removeTeamMember(this)">Remove</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3 text-center">
|
||||
<img src="@(!string.IsNullOrEmpty(Model.TeamMembers[i].PhotoUrl) ? Model.TeamMembers[i].PhotoUrl : "/assets/images/placeholder.jpg")"
|
||||
class="team-member-preview rounded-circle mb-2"
|
||||
style="width: 120px; height: 120px; object-fit: cover; border: 3px solid #6B4E9B;" />
|
||||
<input type="file" class="form-control form-control-sm" accept="image/*" onchange="previewTeamPhoto(this)" />
|
||||
<input type="hidden" name="TeamMembers[@i].PhotoUrl" value="@Model.TeamMembers[i].PhotoUrl" class="team-photo-url" />
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" class="form-control" name="TeamMembers[@i].Name" value="@Model.TeamMembers[i].Name" required />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Role/Position</label>
|
||||
<input type="text" class="form-control" name="TeamMembers[@i].Role" value="@Model.TeamMembers[i].Role" />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Bio</label>
|
||||
<textarea class="form-control" name="TeamMembers[@i].Bio" rows="3">@Model.TeamMembers[i].Bio</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<button class="btn btn-primary" type="submit">Save Changes</button>
|
||||
<a class="btn btn-secondary" href="/admin/pages">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script src="https://cdn.ckeditor.com/ckeditor5/40.1.0/classic/ckeditor.js"></script>
|
||||
<script>
|
||||
ClassicEditor
|
||||
.create(document.querySelector('#pageContent'), {
|
||||
toolbar: {
|
||||
items: [
|
||||
'heading', '|',
|
||||
'bold', 'italic', 'underline', 'strikethrough', '|',
|
||||
'link', 'blockQuote', '|',
|
||||
'bulletedList', 'numberedList', '|',
|
||||
'outdent', 'indent', '|',
|
||||
'alignment', '|',
|
||||
'insertTable', '|',
|
||||
'fontSize', 'fontColor', 'fontBackgroundColor', '|',
|
||||
'removeFormat', '|',
|
||||
'undo', 'redo', '|',
|
||||
'sourceEditing'
|
||||
],
|
||||
shouldNotGroupWhenFull: true
|
||||
},
|
||||
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' },
|
||||
{ model: 'heading4', view: 'h4', title: 'Heading 4', class: 'ck-heading_heading4' }
|
||||
]
|
||||
},
|
||||
fontSize: {
|
||||
options: [
|
||||
'small',
|
||||
'default',
|
||||
'big'
|
||||
]
|
||||
},
|
||||
table: {
|
||||
contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells']
|
||||
},
|
||||
htmlSupport: {
|
||||
allow: [
|
||||
{
|
||||
name: /.*/,
|
||||
attributes: true,
|
||||
classes: true,
|
||||
styles: true
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
// Gallery Image Upload (Multiple)
|
||||
function uploadGalleryImages() {
|
||||
const fileInput = document.getElementById('galleryImageUpload');
|
||||
const files = fileInput.files;
|
||||
|
||||
if (files.length === 0) {
|
||||
alert('Please select at least one image');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show uploading indicator
|
||||
const button = event.target;
|
||||
const originalText = button.innerHTML;
|
||||
button.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Uploading...';
|
||||
button.disabled = true;
|
||||
|
||||
let uploadedCount = 0;
|
||||
let failedCount = 0;
|
||||
|
||||
// Upload each file
|
||||
Array.from(files).forEach((file, index) => {
|
||||
const formData = new FormData();
|
||||
formData.append('image', file);
|
||||
|
||||
fetch('/api/upload/image', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
addGalleryImageToList(data.imageUrl);
|
||||
uploadedCount++;
|
||||
} else {
|
||||
console.error('Upload failed:', data.message);
|
||||
failedCount++;
|
||||
}
|
||||
|
||||
// Check if all uploads are complete
|
||||
if (uploadedCount + failedCount === files.length) {
|
||||
button.innerHTML = originalText;
|
||||
button.disabled = false;
|
||||
fileInput.value = '';
|
||||
|
||||
if (uploadedCount > 0) {
|
||||
alert(`Successfully uploaded ${uploadedCount} image(s)${failedCount > 0 ? `, ${failedCount} failed` : ''}`);
|
||||
} else {
|
||||
alert('All uploads failed. Please try again.');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Upload error:', error);
|
||||
failedCount++;
|
||||
|
||||
if (uploadedCount + failedCount === files.length) {
|
||||
button.innerHTML = originalText;
|
||||
button.disabled = false;
|
||||
fileInput.value = '';
|
||||
alert(`Upload completed. ${uploadedCount} succeeded, ${failedCount} failed.`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function addGalleryImageToList(imageUrl) {
|
||||
const container = document.getElementById('galleryImagesContainer');
|
||||
const count = container.querySelectorAll('.gallery-image-item').length;
|
||||
|
||||
const html = `
|
||||
<div class="col-md-4 gallery-image-item">
|
||||
<div class="card">
|
||||
<img src="${imageUrl}" class="card-img-top" style="height: 150px; object-fit: cover;" />
|
||||
<div class="card-body p-2">
|
||||
<input type="hidden" name="ImageGallery[${count}]" value="${imageUrl}" />
|
||||
<button type="button" class="btn btn-sm btn-danger w-100" onclick="removeGalleryImage(this)">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.insertAdjacentHTML('beforeend', html);
|
||||
}
|
||||
|
||||
function removeGalleryImage(button) {
|
||||
const item = button.closest('.gallery-image-item');
|
||||
item.remove();
|
||||
reindexGalleryImages();
|
||||
}
|
||||
|
||||
function reindexGalleryImages() {
|
||||
const items = document.querySelectorAll('.gallery-image-item');
|
||||
items.forEach((item, index) => {
|
||||
const input = item.querySelector('input[type="hidden"]');
|
||||
input.name = `ImageGallery[${index}]`;
|
||||
});
|
||||
}
|
||||
|
||||
// Team Member Management
|
||||
let teamMemberIndex = document.querySelectorAll('.team-member-card').length;
|
||||
|
||||
function addTeamMember() {
|
||||
const container = document.getElementById('teamMembersContainer');
|
||||
const html = `
|
||||
<div class="card mb-3 team-member-card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">Team Member #${teamMemberIndex + 1}</h6>
|
||||
<button type="button" class="btn btn-sm btn-danger" onclick="removeTeamMember(this)">Remove</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3 text-center">
|
||||
<img src="/assets/images/placeholder.jpg"
|
||||
class="team-member-preview rounded-circle mb-2"
|
||||
style="width: 120px; height: 120px; object-fit: cover; border: 3px solid #6B4E9B;" />
|
||||
<input type="file" class="form-control form-control-sm" accept="image/*" onchange="previewTeamPhoto(this)" />
|
||||
<input type="hidden" name="TeamMembers[${teamMemberIndex}].PhotoUrl" value="" class="team-photo-url" />
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" class="form-control" name="TeamMembers[${teamMemberIndex}].Name" required />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Role/Position</label>
|
||||
<input type="text" class="form-control" name="TeamMembers[${teamMemberIndex}].Role" />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Bio</label>
|
||||
<textarea class="form-control" name="TeamMembers[${teamMemberIndex}].Bio" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.insertAdjacentHTML('beforeend', html);
|
||||
teamMemberIndex++;
|
||||
}
|
||||
|
||||
function removeTeamMember(button) {
|
||||
const card = button.closest('.team-member-card');
|
||||
card.remove();
|
||||
reindexTeamMembers();
|
||||
}
|
||||
|
||||
function reindexTeamMembers() {
|
||||
const cards = document.querySelectorAll('.team-member-card');
|
||||
cards.forEach((card, index) => {
|
||||
card.querySelector('h6').textContent = `Team Member #${index + 1}`;
|
||||
card.querySelectorAll('input, textarea').forEach(input => {
|
||||
const name = input.getAttribute('name');
|
||||
if (name && name.startsWith('TeamMembers[')) {
|
||||
const newName = name.replace(/TeamMembers\[\d+\]/, `TeamMembers[${index}]`);
|
||||
input.setAttribute('name', newName);
|
||||
}
|
||||
});
|
||||
});
|
||||
teamMemberIndex = cards.length;
|
||||
}
|
||||
|
||||
function previewTeamPhoto(input) {
|
||||
const file = input.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const card = input.closest('.team-member-card');
|
||||
const preview = card.querySelector('.team-member-preview');
|
||||
const hiddenInput = card.querySelector('.team-photo-url');
|
||||
|
||||
// Validate file type
|
||||
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
alert('Please select a valid image file (JPG, PNG, GIF, or WebP)');
|
||||
input.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate file size (max 5MB)
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
alert('Image file is too large. Please select an image smaller than 5MB.');
|
||||
input.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Add loading border to preview
|
||||
preview.style.opacity = '0.5';
|
||||
preview.style.border = '3px solid #ffc107';
|
||||
|
||||
// Show preview immediately
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
preview.src = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
// Upload to server
|
||||
const formData = new FormData();
|
||||
formData.append('image', file);
|
||||
|
||||
console.log('Uploading team member photo...');
|
||||
|
||||
fetch('/api/upload/image', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
console.log('Response status:', response.status);
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Upload response:', data);
|
||||
|
||||
preview.style.opacity = '1';
|
||||
|
||||
if (data.success) {
|
||||
hiddenInput.value = data.imageUrl;
|
||||
preview.style.border = '3px solid #28a745';
|
||||
|
||||
// Reset border color after 2 seconds
|
||||
setTimeout(() => {
|
||||
preview.style.border = '3px solid #6B4E9B';
|
||||
}, 2000);
|
||||
|
||||
console.log('Photo uploaded successfully:', data.imageUrl);
|
||||
} else {
|
||||
alert('Upload failed: ' + (data.message || 'Unknown error'));
|
||||
preview.style.border = '3px solid #dc3545';
|
||||
input.value = '';
|
||||
|
||||
// Reset to placeholder after error
|
||||
setTimeout(() => {
|
||||
preview.src = '/assets/images/placeholder.jpg';
|
||||
preview.style.border = '3px solid #6B4E9B';
|
||||
}, 2000);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Upload error:', error);
|
||||
alert('Upload failed. Please check console for details.');
|
||||
preview.style.opacity = '1';
|
||||
preview.style.border = '3px solid #dc3545';
|
||||
input.value = '';
|
||||
|
||||
// Reset to placeholder after error
|
||||
setTimeout(() => {
|
||||
preview.src = '/assets/images/placeholder.jpg';
|
||||
preview.style.border = '3px solid #6B4E9B';
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
}
|
||||
43
Sky_Art_shop/Views/AdminPages/Index.cshtml
Normal file
43
Sky_Art_shop/Views/AdminPages/Index.cshtml
Normal file
@@ -0,0 +1,43 @@
|
||||
@model List<SkyArtShop.Models.Page>
|
||||
@{
|
||||
Layout = "~/Views/Shared/_AdminLayout.cshtml";
|
||||
ViewData["Title"] = "Pages";
|
||||
}
|
||||
<div class="card">
|
||||
<div class="card-body d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Pages</h5>
|
||||
<a class="btn btn-primary" href="/admin/pages/create">Create Page</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Slug</th>
|
||||
<th>Active</th>
|
||||
<th>Updated</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var p in Model)
|
||||
{
|
||||
<tr>
|
||||
<td>@p.PageName</td>
|
||||
<td>@p.PageSlug</td>
|
||||
<td>@(p.IsActive ? "Yes" : "No")</td>
|
||||
<td>@p.UpdatedAt.ToString("MMM dd, yyyy")</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-secondary" href="/admin/pages/edit/@p.Id">Edit</a>
|
||||
<form method="post" action="/admin/pages/delete/@p.Id" class="d-inline" onsubmit="return confirm('Delete this page?');">
|
||||
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user