updateweb
This commit is contained in:
@@ -24,46 +24,46 @@
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html"
|
||||
<a href="/admin/dashboard"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html"
|
||||
<a href="/admin/homepage"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products.html"><i class="bi bi-box"></i> Products</a>
|
||||
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html"
|
||||
<a href="/admin/portfolio"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog.html" class="active"
|
||||
<a href="/admin/blog" class="active"
|
||||
><i class="bi bi-newspaper"></i> Blog</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html"
|
||||
<a href="/admin/pages"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html"
|
||||
<a href="/admin/media-library"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html"><i class="bi bi-list"></i> Menu</a>
|
||||
<a href="/admin/menu"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings.html"><i class="bi bi-gear"></i> Settings</a>
|
||||
<a href="/admin/settings"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users.html"><i class="bi bi-people"></i> Users</a>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
:root {
|
||||
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
--primary-gradient: #202023;
|
||||
--sidebar-width: 250px;
|
||||
--transition-speed: 0.3s;
|
||||
--primary-color: #667eea;
|
||||
@@ -43,6 +43,8 @@ body {
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
color: white;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
@@ -56,26 +58,28 @@ body {
|
||||
}
|
||||
|
||||
.sidebar-menu a {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 15px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.25s ease;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.sidebar-menu a:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: white;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #ffffff;
|
||||
transform: translateX(5px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.sidebar-menu a.active {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
background: #667eea;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.sidebar-menu i {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
/>
|
||||
<style>
|
||||
:root {
|
||||
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
--primary-gradient: #202023;
|
||||
--sidebar-width: 250px;
|
||||
--transition-speed: 0.3s;
|
||||
}
|
||||
@@ -54,6 +54,8 @@
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
color: white;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
@@ -67,26 +69,28 @@
|
||||
}
|
||||
|
||||
.sidebar-menu a {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 15px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.25s ease;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.sidebar-menu a:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: white;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #ffffff;
|
||||
transform: translateX(5px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.sidebar-menu a.active {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
background: #667eea;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.sidebar-menu i {
|
||||
@@ -380,45 +384,43 @@
|
||||
<body>
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<div class="sidebar-brand">Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html" class="active"
|
||||
<a href="/admin/dashboard" class="active"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html"
|
||||
<a href="/admin/homepage"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products.html"><i class="bi bi-box"></i> Products</a>
|
||||
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
<a href="/admin/portfolio"><i class="bi bi-easel"></i> Portfolio</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog.html"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html"><i class="bi bi-file-text"></i> Pages</a>
|
||||
<a href="/admin/pages"><i class="bi bi-file-text"></i> Pages</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html"
|
||||
<a href="/admin/media-library"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html"><i class="bi bi-list"></i> Menu</a>
|
||||
<a href="/admin/menu"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings.html"><i class="bi bi-gear"></i> Settings</a>
|
||||
<a href="/admin/settings"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users.html"><i class="bi bi-people"></i> Users</a>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -432,7 +434,7 @@
|
||||
<p class="mb-0 text-muted">Manage your online shop</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/index.html" target="_blank" class="btn-view-site me-2"
|
||||
<a href="/" target="_blank" class="btn-view-site me-2"
|
||||
><i class="bi bi-eye"></i> View Site</a
|
||||
>
|
||||
<button class="btn-logout" id="logoutBtn">
|
||||
|
||||
@@ -148,44 +148,44 @@
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html"
|
||||
<a href="/admin/dashboard"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html" class="active"
|
||||
<a href="/admin/homepage" class="active"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products.html"><i class="bi bi-box"></i> Products</a>
|
||||
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html"
|
||||
<a href="/admin/portfolio"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog.html"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html"
|
||||
<a href="/admin/pages"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html"
|
||||
<a href="/admin/media-library"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html"><i class="bi bi-list"></i> Menu</a>
|
||||
<a href="/admin/menu"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings.html"><i class="bi bi-gear"></i> Settings</a>
|
||||
<a href="/admin/settings"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users.html"><i class="bi bi-people"></i> Users</a>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -525,11 +525,15 @@ function renderImageVariants() {
|
||||
// Edit product
|
||||
async function editProduct(id) {
|
||||
try {
|
||||
console.log("Loading product for edit, ID:", id);
|
||||
|
||||
const response = await fetch(`/api/admin/products/${id}`, {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log("Product data loaded:", data);
|
||||
|
||||
if (data.success) {
|
||||
const product = data.product;
|
||||
|
||||
@@ -561,6 +565,7 @@ async function editProduct(id) {
|
||||
|
||||
// Load image variants and extract unique product images
|
||||
imageVariants = product.images || [];
|
||||
console.log("Loaded image variants:", imageVariants);
|
||||
|
||||
// Build productImages array from unique image URLs in variants
|
||||
const uniqueImages = {};
|
||||
@@ -579,33 +584,77 @@ async function editProduct(id) {
|
||||
renderImageVariants();
|
||||
|
||||
productModal.show();
|
||||
} else {
|
||||
showError(data.message || "Failed to load product");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load product:", error);
|
||||
showError("Failed to load product details");
|
||||
showError("Failed to load product details: " + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Save product
|
||||
async function saveProduct() {
|
||||
const id = document.getElementById("productId").value;
|
||||
const saveButton = document.getElementById("btnSaveProduct");
|
||||
|
||||
// Disable button and show loading state
|
||||
saveButton.disabled = true;
|
||||
const originalButtonHTML = saveButton.innerHTML;
|
||||
saveButton.innerHTML = '<i class="bi bi-hourglass-split"></i> Saving...';
|
||||
saveButton.style.opacity = "0.7";
|
||||
|
||||
// Get description from Quill editor
|
||||
const description = quillEditor.root.innerHTML;
|
||||
|
||||
// Prepare images array for backend with all new fields
|
||||
const images = imageVariants.map((variant, index) => ({
|
||||
image_url: variant.image_url,
|
||||
color_variant: variant.color_variant || null,
|
||||
color_code: variant.color_code || null,
|
||||
alt_text: variant.alt_text || document.getElementById("productName").value,
|
||||
display_order: index,
|
||||
is_primary: variant.is_primary || false,
|
||||
variant_price: variant.variant_price
|
||||
? parseFloat(variant.variant_price)
|
||||
: null,
|
||||
variant_stock: parseInt(variant.variant_stock) || 0,
|
||||
}));
|
||||
console.log("=== SAVE PRODUCT DEBUG ===");
|
||||
console.log("Product ID:", id);
|
||||
console.log("Image Variants:", imageVariants);
|
||||
console.log("Product Images:", productImages);
|
||||
|
||||
// Prepare images array - include BOTH product images AND color variants
|
||||
const images = [];
|
||||
|
||||
// First, add all color variants (they have color_variant/color_code)
|
||||
imageVariants.forEach((variant, index) => {
|
||||
images.push({
|
||||
image_url: variant.image_url,
|
||||
color_variant: variant.color_variant || null,
|
||||
color_code: variant.color_code || null,
|
||||
alt_text:
|
||||
variant.alt_text || document.getElementById("productName").value,
|
||||
display_order: index,
|
||||
is_primary:
|
||||
variant.is_primary || (index === 0 && imageVariants.length > 0),
|
||||
variant_price: variant.variant_price
|
||||
? parseFloat(variant.variant_price)
|
||||
: null,
|
||||
variant_stock: parseInt(variant.variant_stock) || 0,
|
||||
});
|
||||
});
|
||||
|
||||
// Then add regular product images (without color variants)
|
||||
// Skip images that are already in imageVariants
|
||||
const variantUrls = imageVariants.map((v) => v.image_url);
|
||||
productImages.forEach((img, index) => {
|
||||
if (!variantUrls.includes(img.url)) {
|
||||
images.push({
|
||||
image_url: img.url,
|
||||
color_variant: null,
|
||||
color_code: null,
|
||||
alt_text:
|
||||
img.alt_text ||
|
||||
img.filename ||
|
||||
document.getElementById("productName").value,
|
||||
display_order: imageVariants.length + index,
|
||||
is_primary: images.length === 0, // First image overall is primary
|
||||
variant_price: null,
|
||||
variant_stock: 0,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
console.log("Prepared images array (variants + regular):", images);
|
||||
|
||||
const formData = {
|
||||
name: document.getElementById("productName").value,
|
||||
@@ -624,17 +673,41 @@ async function saveProduct() {
|
||||
images: images,
|
||||
};
|
||||
|
||||
console.log("Form Data:", formData);
|
||||
|
||||
// Validation
|
||||
if (!formData.name || !formData.price) {
|
||||
saveButton.disabled = false;
|
||||
saveButton.innerHTML = originalButtonHTML;
|
||||
saveButton.style.opacity = "1";
|
||||
showError("Please fill in all required fields (Name and Price)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
imageVariants.length > 0 &&
|
||||
imageVariants.some((v) => !v.image_url || !v.color_variant)
|
||||
) {
|
||||
showError("All color variants must have an image and color name selected");
|
||||
// Validate that we have at least one image (either variant or regular)
|
||||
if (images.length === 0) {
|
||||
saveButton.disabled = false;
|
||||
saveButton.innerHTML = originalButtonHTML;
|
||||
saveButton.style.opacity = "1";
|
||||
showError("Please add at least one product image");
|
||||
return;
|
||||
}
|
||||
|
||||
// Only validate image URLs exist
|
||||
if (images.some((img) => !img.image_url)) {
|
||||
saveButton.disabled = false;
|
||||
saveButton.innerHTML = originalButtonHTML;
|
||||
saveButton.style.opacity = "1";
|
||||
showError("All images must have a valid image URL");
|
||||
return;
|
||||
}
|
||||
|
||||
// If a color_variant name is provided, require color_code
|
||||
if (images.some((img) => img.color_variant && !img.color_code)) {
|
||||
saveButton.disabled = false;
|
||||
saveButton.innerHTML = originalButtonHTML;
|
||||
saveButton.style.opacity = "1";
|
||||
showError("All color variants must have both a color name and color code");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -642,6 +715,8 @@ async function saveProduct() {
|
||||
const url = id ? `/api/admin/products/${id}` : "/api/admin/products";
|
||||
const method = id ? "PUT" : "POST";
|
||||
|
||||
console.log("Saving product:", method, url, formData);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
@@ -651,21 +726,58 @@ async function saveProduct() {
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
console.log("Response status:", response.status);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error("Response error:", errorText);
|
||||
throw new Error(`Server error: ${response.status} - ${errorText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log("Response data:", data);
|
||||
|
||||
if (data.success) {
|
||||
// Change button to success state briefly
|
||||
saveButton.innerHTML = '<i class="bi bi-check-circle-fill"></i> Saved!';
|
||||
saveButton.style.background = "#10b981";
|
||||
|
||||
// Show success toast
|
||||
showSuccess(
|
||||
id
|
||||
? "Product updated successfully! 🎉"
|
||||
: "Product created successfully! 🎉"
|
||||
? "✅ Product Updated Successfully! Changes are now live on your website."
|
||||
: "✅ Product Created Successfully! Now visible on your shop page."
|
||||
);
|
||||
productModal.hide();
|
||||
loadProducts();
|
||||
|
||||
// Wait a moment then close modal
|
||||
setTimeout(async () => {
|
||||
productModal.hide();
|
||||
|
||||
// Reload products to show updated data
|
||||
await loadProducts();
|
||||
|
||||
// Clear form for next use
|
||||
imageVariants = [];
|
||||
productImages = [];
|
||||
|
||||
// Reset button
|
||||
saveButton.disabled = false;
|
||||
saveButton.innerHTML = originalButtonHTML;
|
||||
saveButton.style.opacity = "1";
|
||||
saveButton.style.background = "";
|
||||
}, 1000);
|
||||
} else {
|
||||
saveButton.disabled = false;
|
||||
saveButton.innerHTML = originalButtonHTML;
|
||||
saveButton.style.opacity = "1";
|
||||
showError(data.message || "Failed to save product");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save product:", error);
|
||||
showError("Failed to save product");
|
||||
saveButton.disabled = false;
|
||||
saveButton.innerHTML = originalButtonHTML;
|
||||
saveButton.style.opacity = "1";
|
||||
showError(`Failed to save product: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -841,14 +953,18 @@ function formatDate(dateString) {
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
console.log("✅ SUCCESS:", message);
|
||||
showToast(message, "success");
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
console.error("❌ ERROR:", message);
|
||||
showToast(message, "error");
|
||||
}
|
||||
|
||||
function showToast(message, type = "info") {
|
||||
console.log("📢 Showing toast:", type, message);
|
||||
|
||||
// Create toast container if it doesn't exist
|
||||
let container = document.getElementById("toastContainer");
|
||||
if (!container) {
|
||||
@@ -856,6 +972,7 @@ function showToast(message, type = "info") {
|
||||
container.id = "toastContainer";
|
||||
container.className = "toast-container";
|
||||
document.body.appendChild(container);
|
||||
console.log("Created toast container");
|
||||
}
|
||||
|
||||
// Create toast element
|
||||
@@ -883,6 +1000,7 @@ function showToast(message, type = "info") {
|
||||
`;
|
||||
|
||||
container.appendChild(toast);
|
||||
console.log("Toast appended to container");
|
||||
|
||||
// Auto remove after 4 seconds
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -84,19 +84,19 @@ function renderUsers(users) {
|
||||
${u.isactive ? "Active" : "Disabled"}</span></td>
|
||||
<td>${formatDate(u.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editUser(${
|
||||
<button class="btn btn-sm btn-info" onclick="editUser('${escapeHtml(
|
||||
u.id
|
||||
})" title="Edit User">
|
||||
)}')" title="Edit User">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-warning" onclick="showChangePassword(${
|
||||
<button class="btn btn-sm btn-warning" onclick="showChangePassword('${escapeHtml(
|
||||
u.id
|
||||
}, '${escapeHtml(u.name)}')" title="Change Password">
|
||||
)}', '${escapeHtml(u.name)}')" title="Change Password">
|
||||
<i class="bi bi-key"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteUser(${
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteUser('${escapeHtml(
|
||||
u.id
|
||||
}, '${escapeHtml(u.name)}')" title="Delete User">
|
||||
)}', '${escapeHtml(u.name)}')" title="Delete User">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
@@ -195,6 +195,8 @@ async function saveUser() {
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading(id ? "Updating user..." : "Creating user...");
|
||||
|
||||
try {
|
||||
const url = id ? `/api/admin/users/${id}` : "/api/admin/users";
|
||||
const method = id ? "PUT" : "POST";
|
||||
@@ -206,6 +208,8 @@ async function saveUser() {
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
hideLoading();
|
||||
|
||||
if (data.success) {
|
||||
showSuccess(
|
||||
id ? "User updated successfully" : "User created successfully"
|
||||
@@ -217,6 +221,7 @@ async function saveUser() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save user:", error);
|
||||
hideLoading();
|
||||
showError("Failed to save user");
|
||||
}
|
||||
}
|
||||
@@ -249,6 +254,8 @@ async function changePassword() {
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading("Changing password...");
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${id}/password`, {
|
||||
method: "PUT",
|
||||
@@ -258,6 +265,8 @@ async function changePassword() {
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
hideLoading();
|
||||
|
||||
if (data.success) {
|
||||
showSuccess("Password changed successfully");
|
||||
passwordModal.hide();
|
||||
@@ -266,6 +275,7 @@ async function changePassword() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to change password:", error);
|
||||
hideLoading();
|
||||
showError("Failed to change password");
|
||||
}
|
||||
}
|
||||
@@ -278,12 +288,16 @@ async function deleteUser(id, name) {
|
||||
)
|
||||
return;
|
||||
|
||||
showLoading("Deleting user...");
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
hideLoading();
|
||||
|
||||
if (data.success) {
|
||||
showSuccess("User deleted successfully");
|
||||
loadUsers();
|
||||
@@ -292,6 +306,7 @@ async function deleteUser(id, name) {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete user:", error);
|
||||
hideLoading();
|
||||
showError("Failed to delete user");
|
||||
}
|
||||
}
|
||||
@@ -332,9 +347,76 @@ function formatDate(dateString) {
|
||||
});
|
||||
}
|
||||
|
||||
// Custom notification system
|
||||
function showNotification(type, title, message) {
|
||||
const container = document.getElementById("notificationContainer");
|
||||
const notification = document.createElement("div");
|
||||
notification.className = `notification notification-${type}`;
|
||||
|
||||
const icon =
|
||||
type === "success"
|
||||
? '<i class="bi bi-check-circle-fill"></i>'
|
||||
: '<i class="bi bi-exclamation-circle-fill"></i>';
|
||||
|
||||
notification.innerHTML = `
|
||||
<div class="notification-icon">${icon}</div>
|
||||
<div class="notification-content">
|
||||
<div class="notification-title">${title}</div>
|
||||
<div class="notification-message">${message}</div>
|
||||
</div>
|
||||
<button class="notification-close" onclick="closeNotification(this)">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
`;
|
||||
|
||||
container.appendChild(notification);
|
||||
|
||||
// Auto remove after 3 seconds
|
||||
setTimeout(() => {
|
||||
if (notification.parentElement) {
|
||||
notification.classList.add("removing");
|
||||
setTimeout(() => {
|
||||
if (notification.parentElement) {
|
||||
notification.remove();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function closeNotification(button) {
|
||||
const notification = button.closest(".notification");
|
||||
notification.classList.add("removing");
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
alert(message);
|
||||
showNotification("success", "Success!", message);
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
alert("Error: " + message);
|
||||
showNotification("error", "Error", message);
|
||||
}
|
||||
|
||||
// Loading overlay functions
|
||||
function showLoading(message = "Saving...") {
|
||||
const overlay = document.createElement("div");
|
||||
overlay.className = "loading-overlay";
|
||||
overlay.id = "loadingOverlay";
|
||||
overlay.innerHTML = `
|
||||
<div class="loading-spinner-container">
|
||||
<div class="loading-spinner-icon"></div>
|
||||
<div class="loading-spinner-text">${message}</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
const overlay = document.getElementById("loadingOverlay");
|
||||
if (overlay) {
|
||||
overlay.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,121 +5,261 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Admin Login - Sky Art Shop</title>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.css"
|
||||
/>
|
||||
<style>
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
.login-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
.logo-section {
|
||||
flex: 1;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
.login-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
padding: 40px;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
.login-header {
|
||||
.logo-content {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
max-width: 1100px;
|
||||
}
|
||||
.login-header h1 {
|
||||
color: #2c3e50;
|
||||
font-size: 28px;
|
||||
.logo-image {
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
height: auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.form-section {
|
||||
flex: 1;
|
||||
background-color: #ffd0d0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.login-box {
|
||||
background: white;
|
||||
border: 2px solid #333;
|
||||
padding: 60px 50px;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.login-title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
.login-header p {
|
||||
color: #7f8c8d;
|
||||
margin: 0;
|
||||
.login-subtitle {
|
||||
font-size: 1.1rem;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.form-label {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.form-control {
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border: 2px solid #ddd;
|
||||
padding: 12px 15px;
|
||||
font-size: 1rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.form-control:focus {
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
|
||||
border-color: #333;
|
||||
box-shadow: none;
|
||||
}
|
||||
.btn-group-custom {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.btn-custom {
|
||||
flex: 1;
|
||||
padding: 12px 20px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
border: 2px solid #333;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.btn-login {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
width: 100%;
|
||||
transition: transform 0.2s;
|
||||
background-color: white;
|
||||
color: #333;
|
||||
}
|
||||
.btn-login:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||
background-color: #333;
|
||||
color: white;
|
||||
}
|
||||
.btn-login:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
.btn-reset {
|
||||
background-color: white;
|
||||
color: #333;
|
||||
}
|
||||
.btn-reset:hover {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
}
|
||||
.back-link {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.back-link a {
|
||||
color: #ff6b6b;
|
||||
text-decoration: none;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.back-link a:hover {
|
||||
color: #ff5252;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.alert {
|
||||
border-radius: 8px;
|
||||
margin-bottom: 25px;
|
||||
border-radius: 4px;
|
||||
padding: 12px 15px;
|
||||
display: none;
|
||||
}
|
||||
.alert.show {
|
||||
display: block;
|
||||
}
|
||||
@media (max-width: 992px) {
|
||||
.login-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
.logo-section,
|
||||
.form-section {
|
||||
flex: none;
|
||||
height: 50vh;
|
||||
}
|
||||
.color-code {
|
||||
font-size: 18px;
|
||||
}
|
||||
.login-box {
|
||||
padding: 40px 30px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 576px) {
|
||||
.login-box {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
.login-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-card">
|
||||
<div class="login-header">
|
||||
<h1>🛍️ Sky Art Shop</h1>
|
||||
<p>Admin Panel Login</p>
|
||||
<div class="login-container">
|
||||
<!-- Left Section - Logo -->
|
||||
<div class="logo-section">
|
||||
<div class="logo-content">
|
||||
<img
|
||||
src="/uploads/cat-logo-template-page-20251224-194356-0000-1766724728795-173839741.png"
|
||||
alt="Sky Art Shop Logo"
|
||||
class="logo-image"
|
||||
onerror="this.style.display='none'; this.nextElementSibling.style.display='block';"
|
||||
/>
|
||||
<div style="display: none">
|
||||
<svg
|
||||
width="400"
|
||||
height="300"
|
||||
viewBox="0 0 400 300"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<!-- Cat Silhouette -->
|
||||
<path
|
||||
d="M120 80 Q100 60 90 80 L80 100 Q70 120 80 140 L100 160 Q110 180 140 180 L160 185 Q180 190 200 180 L220 160 Q230 140 220 120 L210 100 Q200 80 180 80 Z"
|
||||
fill="#000"
|
||||
/>
|
||||
<circle cx="110" cy="100" r="8" fill="#000" />
|
||||
<circle cx="170" cy="100" r="8" fill="#000" />
|
||||
<!-- Text -->
|
||||
<text
|
||||
x="50"
|
||||
y="240"
|
||||
font-family="'Brush Script MT', cursive"
|
||||
font-size="48"
|
||||
fill="#000"
|
||||
>
|
||||
Sky Art Shop
|
||||
</text>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-danger" role="alert" id="errorAlert"></div>
|
||||
<!-- Right Section - Form -->
|
||||
<div class="form-section">
|
||||
<div class="login-box">
|
||||
<h1 class="login-title">Sky Art Shop</h1>
|
||||
<p class="login-subtitle">Admin Panel Login</p>
|
||||
|
||||
<form id="loginForm">
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email Address</label>
|
||||
<input
|
||||
type="email"
|
||||
class="form-control"
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
placeholder="admin@example.com"
|
||||
autocomplete="username"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
id="password"
|
||||
name="password"
|
||||
required
|
||||
placeholder="Enter your password"
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-login" id="loginBtn">Sign In</button>
|
||||
</form>
|
||||
<div class="alert alert-danger" role="alert" id="errorAlert"></div>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<a
|
||||
href="/home.html"
|
||||
class="text-decoration-none"
|
||||
style="color: #667eea"
|
||||
>← Back to Website</a
|
||||
>
|
||||
<form id="loginForm">
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Username:</label>
|
||||
<input
|
||||
type="email"
|
||||
class="form-control"
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
placeholder="Enter your email"
|
||||
autocomplete="username"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password:</label>
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
id="password"
|
||||
name="password"
|
||||
required
|
||||
placeholder="Enter your password"
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="btn-group-custom">
|
||||
<button type="submit" class="btn-custom btn-login" id="loginBtn">
|
||||
Login
|
||||
</button>
|
||||
<button type="reset" class="btn-custom btn-reset">Reset</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="back-link">
|
||||
<a href="/">Back to Website</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
document
|
||||
.getElementById("loginForm")
|
||||
@@ -133,8 +273,8 @@
|
||||
|
||||
// Disable button during login
|
||||
loginBtn.disabled = true;
|
||||
loginBtn.textContent = "Signing in...";
|
||||
errorAlert.style.display = "none";
|
||||
loginBtn.textContent = "Logging in...";
|
||||
errorAlert.classList.remove("show");
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/admin/login", {
|
||||
@@ -150,20 +290,23 @@
|
||||
|
||||
if (response.ok && data.success) {
|
||||
// Login successful - redirect to dashboard
|
||||
window.location.href = "/admin/dashboard.html";
|
||||
window.location.href = "/admin/dashboard";
|
||||
} else {
|
||||
// Show error
|
||||
errorAlert.textContent = data.message || "Invalid credentials";
|
||||
errorAlert.style.display = "block";
|
||||
errorAlert.innerHTML =
|
||||
'<i class="bi bi-exclamation-triangle"></i> ' +
|
||||
(data.message || "Invalid credentials");
|
||||
errorAlert.classList.add("show");
|
||||
loginBtn.disabled = false;
|
||||
loginBtn.textContent = "Sign In";
|
||||
loginBtn.textContent = "Login";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Login error:", error);
|
||||
errorAlert.textContent = "Login failed. Please try again.";
|
||||
errorAlert.style.display = "block";
|
||||
errorAlert.innerHTML =
|
||||
'<i class="bi bi-exclamation-triangle"></i> Login failed. Please try again.';
|
||||
errorAlert.classList.add("show");
|
||||
loginBtn.disabled = false;
|
||||
loginBtn.textContent = "Sign In";
|
||||
loginBtn.textContent = "Login";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -475,44 +475,44 @@
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html"
|
||||
<a href="/admin/dashboard"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html"
|
||||
<a href="/admin/homepage"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products.html"><i class="bi bi-box"></i> Products</a>
|
||||
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html"
|
||||
<a href="/admin/portfolio"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog.html"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html"
|
||||
<a href="/admin/pages"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html" class="active"
|
||||
<a href="/admin/media-library" class="active"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html"><i class="bi bi-list"></i> Menu</a>
|
||||
<a href="/admin/menu"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings.html"><i class="bi bi-gear"></i> Settings</a>
|
||||
<a href="/admin/settings"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users.html"><i class="bi bi-people"></i> Users</a>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -790,7 +790,7 @@
|
||||
);
|
||||
allFolders = [];
|
||||
if (data.message && data.message.includes("Authentication")) {
|
||||
window.location.href = "/admin/login.html";
|
||||
window.location.href = "/admin/login";
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -833,7 +833,7 @@
|
||||
console.error("Failed to load files:", data.message || data.error);
|
||||
allFiles = [];
|
||||
if (data.message && data.message.includes("Authentication")) {
|
||||
window.location.href = "/admin/login.html";
|
||||
window.location.href = "/admin/login";
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -49,46 +49,46 @@
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html"
|
||||
<a href="/admin/dashboard"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html"
|
||||
<a href="/admin/homepage"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products.html"><i class="bi bi-box"></i> Products</a>
|
||||
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html"
|
||||
<a href="/admin/portfolio"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog.html"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html"
|
||||
<a href="/admin/pages"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html"
|
||||
<a href="/admin/media-library"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html" class="active"
|
||||
<a href="/admin/menu" class="active"
|
||||
><i class="bi bi-list"></i> Menu</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings.html"><i class="bi bi-gear"></i> Settings</a>
|
||||
<a href="/admin/settings"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users.html"><i class="bi bi-people"></i> Users</a>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -233,44 +233,44 @@
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html"
|
||||
<a href="/admin/dashboard"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html"
|
||||
<a href="/admin/homepage"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products.html"><i class="bi bi-box"></i> Products</a>
|
||||
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html"
|
||||
<a href="/admin/portfolio"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog.html"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html" class="active"
|
||||
<a href="/admin/pages" class="active"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html"
|
||||
<a href="/admin/media-library"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html"><i class="bi bi-list"></i> Menu</a>
|
||||
<a href="/admin/menu"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings.html"><i class="bi bi-gear"></i> Settings</a>
|
||||
<a href="/admin/settings"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users.html"><i class="bi bi-people"></i> Users</a>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -24,44 +24,44 @@
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html"
|
||||
<a href="/admin/dashboard"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html"
|
||||
<a href="/admin/homepage"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products.html"><i class="bi bi-box"></i> Products</a>
|
||||
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html" class="active"
|
||||
<a href="/admin/portfolio" class="active"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog.html"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html"
|
||||
<a href="/admin/pages"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html"
|
||||
<a href="/admin/media-library"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html"><i class="bi bi-list"></i> Menu</a>
|
||||
<a href="/admin/menu"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings.html"><i class="bi bi-gear"></i> Settings</a>
|
||||
<a href="/admin/settings"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users.html"><i class="bi bi-people"></i> Users</a>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -25,46 +25,46 @@
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html"
|
||||
<a href="/admin/dashboard"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html"
|
||||
<a href="/admin/homepage"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products.html" class="active"
|
||||
<a href="/admin/products" class="active"
|
||||
><i class="bi bi-box"></i> Products</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html"
|
||||
<a href="/admin/portfolio"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog.html"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html"
|
||||
<a href="/admin/pages"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html"
|
||||
<a href="/admin/media-library"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html"><i class="bi bi-list"></i> Menu</a>
|
||||
<a href="/admin/menu"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings.html"><i class="bi bi-gear"></i> Settings</a>
|
||||
<a href="/admin/settings"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users.html"><i class="bi bi-people"></i> Users</a>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -124,46 +124,46 @@
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html"
|
||||
<a href="/admin/dashboard"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html"
|
||||
<a href="/admin/homepage"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products.html"><i class="bi bi-box"></i> Products</a>
|
||||
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html"
|
||||
<a href="/admin/portfolio"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog.html"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html"
|
||||
<a href="/admin/pages"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html"
|
||||
<a href="/admin/media-library"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html"><i class="bi bi-list"></i> Menu</a>
|
||||
<a href="/admin/menu"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings.html" class="active"
|
||||
<a href="/admin/settings" class="active"
|
||||
><i class="bi bi-gear"></i> Settings</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users.html"><i class="bi bi-people"></i> Users</a>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -115,43 +115,43 @@
|
||||
</button>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
<a href="/admin/dashboard.html" class="nav-item">
|
||||
<a href="/admin/dashboard" class="nav-item">
|
||||
<i class="bi bi-speedometer2"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
<a href="/admin/products.html" class="nav-item">
|
||||
<a href="/admin/products" class="nav-item">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
<span>Products</span>
|
||||
</a>
|
||||
<a href="/admin/portfolio.html" class="nav-item">
|
||||
<a href="/admin/portfolio" class="nav-item">
|
||||
<i class="bi bi-images"></i>
|
||||
<span>Portfolio</span>
|
||||
</a>
|
||||
<a href="/admin/blog.html" class="nav-item">
|
||||
<a href="/admin/blog" class="nav-item">
|
||||
<i class="bi bi-file-text"></i>
|
||||
<span>Blog</span>
|
||||
</a>
|
||||
<a href="/admin/pages.html" class="nav-item">
|
||||
<a href="/admin/pages" class="nav-item">
|
||||
<i class="bi bi-file-earmark"></i>
|
||||
<span>Pages</span>
|
||||
</a>
|
||||
<a href="/admin/team-members.html" class="nav-item active">
|
||||
<a href="/admin/team-members" class="nav-item active">
|
||||
<i class="bi bi-people"></i>
|
||||
<span>Team Members</span>
|
||||
</a>
|
||||
<a href="/admin/media-library.html" class="nav-item">
|
||||
<a href="/admin/media-library" class="nav-item">
|
||||
<i class="bi bi-image"></i>
|
||||
<span>Media Library</span>
|
||||
</a>
|
||||
<a href="/admin/menu.html" class="nav-item">
|
||||
<a href="/admin/menu" class="nav-item">
|
||||
<i class="bi bi-list"></i>
|
||||
<span>Menu</span>
|
||||
</a>
|
||||
<a href="/admin/users.html" class="nav-item">
|
||||
<a href="/admin/users" class="nav-item">
|
||||
<i class="bi bi-person"></i>
|
||||
<span>Users</span>
|
||||
</a>
|
||||
<a href="/admin/settings.html" class="nav-item">
|
||||
<a href="/admin/settings" class="nav-item">
|
||||
<i class="bi bi-gear"></i>
|
||||
<span>Settings</span>
|
||||
</a>
|
||||
|
||||
465
website/admin/test-notifications.html
Normal file
465
website/admin/test-notifications.html
Normal file
@@ -0,0 +1,465 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Notification System Demo</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
/>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.demo-container {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.subtitle {
|
||||
color: #666;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.button-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
button {
|
||||
padding: 15px 25px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.btn-success {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
}
|
||||
.btn-success:hover {
|
||||
background: #059669;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
.btn-error {
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
}
|
||||
.btn-error:hover {
|
||||
background: #dc2626;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
.btn-loading {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
.btn-loading:hover {
|
||||
background: #5568d3;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
.info-box {
|
||||
background: #eff6ff;
|
||||
border-left: 4px solid #3b82f6;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.info-box h3 {
|
||||
margin-top: 0;
|
||||
color: #1e40af;
|
||||
}
|
||||
.info-box ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.info-box li {
|
||||
margin-bottom: 8px;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
/* Notification Styles (copied from users.html) */
|
||||
#notificationContainer {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 9999;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.notification {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
background: white;
|
||||
border-left: 4px solid;
|
||||
animation: slideIn 0.3s ease-out, fadeOut 0.3s ease-in 2.7s;
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
min-width: 320px;
|
||||
}
|
||||
|
||||
.notification.removing {
|
||||
animation: slideOut 0.3s ease-in;
|
||||
}
|
||||
|
||||
.notification-success {
|
||||
border-left-color: #10b981;
|
||||
background: linear-gradient(to right, #f0fdf4 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
.notification-error {
|
||||
border-left-color: #ef4444;
|
||||
background: linear-gradient(to right, #fef2f2 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
.notification-icon {
|
||||
font-size: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.notification-success .notification-icon {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.notification-error .notification-icon {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.notification-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.notification-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.notification-message {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.notification-close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 20px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.notification-close:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(400px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideOut {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: translateX(400px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9998;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.loading-spinner-container {
|
||||
background: white;
|
||||
padding: 30px 40px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-spinner-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #667eea;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 15px;
|
||||
}
|
||||
|
||||
.loading-spinner-text {
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="demo-container">
|
||||
<h1>🎨 Custom Notification System</h1>
|
||||
<p class="subtitle">
|
||||
Test the new notification system for user management
|
||||
</p>
|
||||
|
||||
<div class="button-grid">
|
||||
<button class="btn-success" onclick="testSuccess()">
|
||||
<i class="bi bi-check-circle"></i>
|
||||
Show Success
|
||||
</button>
|
||||
<button class="btn-error" onclick="testError()">
|
||||
<i class="bi bi-x-circle"></i>
|
||||
Show Error
|
||||
</button>
|
||||
<button class="btn-loading" onclick="testLoading()">
|
||||
<i class="bi bi-hourglass-split"></i>
|
||||
Show Loading
|
||||
</button>
|
||||
<button class="btn-success" onclick="testMultiple()">
|
||||
<i class="bi bi-stack"></i>
|
||||
Multiple Notifications
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<h3>✨ Features</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Custom Design:</strong> Beautiful, non-intrusive
|
||||
notifications
|
||||
</li>
|
||||
<li>
|
||||
<strong>Auto-dismiss:</strong> Notifications automatically fade
|
||||
after 3 seconds
|
||||
</li>
|
||||
<li>
|
||||
<strong>Manual Close:</strong> Click the X button to close
|
||||
immediately
|
||||
</li>
|
||||
<li>
|
||||
<strong>Loading Overlay:</strong> Full-screen loading indicator with
|
||||
spinner
|
||||
</li>
|
||||
<li>
|
||||
<strong>Smooth Animations:</strong> Slide-in and slide-out
|
||||
transitions
|
||||
</li>
|
||||
<li>
|
||||
<strong>Stack Support:</strong> Multiple notifications stack nicely
|
||||
</li>
|
||||
<li>
|
||||
<strong>No Browser Alerts:</strong> Replaced with styled, modern
|
||||
notifications
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="info-box"
|
||||
style="
|
||||
background: #fef9c3;
|
||||
border-left-color: #eab308;
|
||||
margin-top: 15px;
|
||||
"
|
||||
>
|
||||
<h3 style="color: #854d0e">💡 Usage in User Management</h3>
|
||||
<ul>
|
||||
<li>✅ Creating user → Shows loading, then success notification</li>
|
||||
<li>✅ Updating user → Shows loading, then success notification</li>
|
||||
<li>
|
||||
✅ Changing password → Shows loading, then success notification
|
||||
</li>
|
||||
<li>✅ Deleting user → Shows loading, then success notification</li>
|
||||
<li>❌ Any error → Shows error notification with details</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notification Container -->
|
||||
<div id="notificationContainer"></div>
|
||||
|
||||
<script>
|
||||
// Custom notification system
|
||||
function showNotification(type, title, message) {
|
||||
const container = document.getElementById("notificationContainer");
|
||||
const notification = document.createElement("div");
|
||||
notification.className = `notification notification-${type}`;
|
||||
|
||||
const icon =
|
||||
type === "success"
|
||||
? '<i class="bi bi-check-circle-fill"></i>'
|
||||
: '<i class="bi bi-exclamation-circle-fill"></i>';
|
||||
|
||||
notification.innerHTML = `
|
||||
<div class="notification-icon">${icon}</div>
|
||||
<div class="notification-content">
|
||||
<div class="notification-title">${title}</div>
|
||||
<div class="notification-message">${message}</div>
|
||||
</div>
|
||||
<button class="notification-close" onclick="closeNotification(this)">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
`;
|
||||
|
||||
container.appendChild(notification);
|
||||
|
||||
// Auto remove after 3 seconds
|
||||
setTimeout(() => {
|
||||
if (notification.parentElement) {
|
||||
notification.classList.add("removing");
|
||||
setTimeout(() => {
|
||||
if (notification.parentElement) {
|
||||
notification.remove();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function closeNotification(button) {
|
||||
const notification = button.closest(".notification");
|
||||
notification.classList.add("removing");
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function showLoading(message = "Loading...") {
|
||||
const overlay = document.createElement("div");
|
||||
overlay.className = "loading-overlay";
|
||||
overlay.id = "loadingOverlay";
|
||||
overlay.innerHTML = `
|
||||
<div class="loading-spinner-container">
|
||||
<div class="loading-spinner-icon"></div>
|
||||
<div class="loading-spinner-text">${message}</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
const overlay = document.getElementById("loadingOverlay");
|
||||
if (overlay) {
|
||||
overlay.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Test functions
|
||||
function testSuccess() {
|
||||
showNotification(
|
||||
"success",
|
||||
"Success!",
|
||||
"User has been saved successfully"
|
||||
);
|
||||
}
|
||||
|
||||
function testError() {
|
||||
showNotification(
|
||||
"error",
|
||||
"Error",
|
||||
"Failed to save user. Please try again."
|
||||
);
|
||||
}
|
||||
|
||||
function testLoading() {
|
||||
showLoading("Saving user...");
|
||||
setTimeout(() => {
|
||||
hideLoading();
|
||||
showNotification(
|
||||
"success",
|
||||
"Complete!",
|
||||
"Operation finished successfully"
|
||||
);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function testMultiple() {
|
||||
showNotification("success", "User Created", "John Doe has been added");
|
||||
setTimeout(() => {
|
||||
showNotification(
|
||||
"success",
|
||||
"Email Sent",
|
||||
"Welcome email has been sent"
|
||||
);
|
||||
}, 500);
|
||||
setTimeout(() => {
|
||||
showNotification(
|
||||
"success",
|
||||
"Permissions Set",
|
||||
"User permissions configured"
|
||||
);
|
||||
}, 1000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
<div class="test-section">
|
||||
<h3>Test 3: Navigate to Products Page</h3>
|
||||
<a href="/admin/products.html" class="btn btn-primary">
|
||||
<a href="/admin/products" class="btn btn-primary">
|
||||
🛍️ Go to Products Management
|
||||
</a>
|
||||
</div>
|
||||
|
||||
358
website/admin/test-user-api.html
Normal file
358
website/admin/test-user-api.html
Normal file
@@ -0,0 +1,358 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>User Management API Tests</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
max-width: 1200px;
|
||||
margin: 40px auto;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
border-bottom: 3px solid #667eea;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.test-section {
|
||||
margin: 30px 0;
|
||||
padding: 20px;
|
||||
background: #f9f9f9;
|
||||
border-left: 4px solid #667eea;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.test-section h3 {
|
||||
margin-top: 0;
|
||||
color: #667eea;
|
||||
}
|
||||
button {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin: 5px;
|
||||
}
|
||||
button:hover {
|
||||
background: #5568d3;
|
||||
}
|
||||
.result {
|
||||
margin-top: 15px;
|
||||
padding: 15px;
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
font-family: "Courier New", monospace;
|
||||
font-size: 13px;
|
||||
white-space: pre-wrap;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.success {
|
||||
border-left: 4px solid #10b981;
|
||||
background: #f0fdf4;
|
||||
}
|
||||
.error {
|
||||
border-left: 4px solid #ef4444;
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
}
|
||||
.info {
|
||||
background: #eff6ff;
|
||||
border-left: 4px solid #3b82f6;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
input {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
margin: 5px;
|
||||
width: 200px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🧪 User Management API Tests</h1>
|
||||
|
||||
<div class="info">
|
||||
<strong>Note:</strong> You must be logged in as an admin to run these
|
||||
tests. Open the browser console (F12) for detailed logs.
|
||||
</div>
|
||||
|
||||
<!-- Test 1: List All Users -->
|
||||
<div class="test-section">
|
||||
<h3>1. List All Users (GET /api/admin/users)</h3>
|
||||
<button onclick="testListUsers()">Run Test</button>
|
||||
<div id="result1" class="result" style="display: none"></div>
|
||||
</div>
|
||||
|
||||
<!-- Test 2: Get Single User -->
|
||||
<div class="test-section">
|
||||
<h3>2. Get Single User (GET /api/admin/users/:id)</h3>
|
||||
<input type="text" id="getUserId" placeholder="Enter user ID" />
|
||||
<button onclick="testGetUser()">Run Test</button>
|
||||
<div id="result2" class="result" style="display: none"></div>
|
||||
</div>
|
||||
|
||||
<!-- Test 3: Create New User -->
|
||||
<div class="test-section">
|
||||
<h3>3. Create New User (POST /api/admin/users)</h3>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
id="createName"
|
||||
placeholder="Name"
|
||||
value="Test User"
|
||||
/>
|
||||
<input type="text" id="createUsername" placeholder="Username" />
|
||||
<input type="email" id="createEmail" placeholder="Email" />
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="password"
|
||||
id="createPassword"
|
||||
placeholder="Password"
|
||||
value="SecurePass123"
|
||||
/>
|
||||
<select id="createRole" style="padding: 8px; margin: 5px">
|
||||
<option value="Cashier">Cashier</option>
|
||||
<option value="Accountant">Accountant</option>
|
||||
<option value="Admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<button onclick="testCreateUser()">Run Test</button>
|
||||
<div id="result3" class="result" style="display: none"></div>
|
||||
</div>
|
||||
|
||||
<!-- Test 4: Update User -->
|
||||
<div class="test-section">
|
||||
<h3>4. Update User (PUT /api/admin/users/:id)</h3>
|
||||
<input type="text" id="updateUserId" placeholder="User ID" />
|
||||
<input type="text" id="updateName" placeholder="New Name" />
|
||||
<select id="updateRole" style="padding: 8px; margin: 5px">
|
||||
<option value="Cashier">Cashier</option>
|
||||
<option value="Accountant">Accountant</option>
|
||||
<option value="Admin">Admin</option>
|
||||
</select>
|
||||
<button onclick="testUpdateUser()">Run Test</button>
|
||||
<div id="result4" class="result" style="display: none"></div>
|
||||
</div>
|
||||
|
||||
<!-- Test 5: Change Password -->
|
||||
<div class="test-section">
|
||||
<h3>5. Change Password (PUT /api/admin/users/:id/password)</h3>
|
||||
<input type="text" id="passwordUserId" placeholder="User ID" />
|
||||
<input
|
||||
type="password"
|
||||
id="newPassword"
|
||||
placeholder="New Password"
|
||||
value="NewSecure456"
|
||||
/>
|
||||
<button onclick="testChangePassword()">Run Test</button>
|
||||
<div id="result5" class="result" style="display: none"></div>
|
||||
</div>
|
||||
|
||||
<!-- Test 6: Delete User -->
|
||||
<div class="test-section">
|
||||
<h3>6. Delete User (DELETE /api/admin/users/:id)</h3>
|
||||
<input type="text" id="deleteUserId" placeholder="User ID" />
|
||||
<button onclick="testDeleteUser()" style="background: #ef4444">
|
||||
Run Test
|
||||
</button>
|
||||
<div id="result6" class="result" style="display: none"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Generate random username/email
|
||||
function generateTestCredentials() {
|
||||
const timestamp = Date.now();
|
||||
document.getElementById("createUsername").value =
|
||||
"testuser_" + timestamp;
|
||||
document.getElementById("createEmail").value =
|
||||
"test_" + timestamp + "@example.com";
|
||||
}
|
||||
|
||||
// Initialize with random credentials
|
||||
generateTestCredentials();
|
||||
|
||||
// Helper to display results
|
||||
function showResult(elementId, data, isError = false) {
|
||||
const resultDiv = document.getElementById(elementId);
|
||||
resultDiv.style.display = "block";
|
||||
resultDiv.className = "result " + (isError ? "error" : "success");
|
||||
resultDiv.textContent =
|
||||
typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
// Test 1: List all users
|
||||
async function testListUsers() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/users", {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log("List Users:", data);
|
||||
showResult("result1", data);
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
showResult("result1", error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Test 2: Get single user
|
||||
async function testGetUser() {
|
||||
const userId = document.getElementById("getUserId").value;
|
||||
if (!userId) {
|
||||
alert("Please enter a user ID");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${userId}`, {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log("Get User:", data);
|
||||
showResult("result2", data);
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
showResult("result2", error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Test 3: Create user
|
||||
async function testCreateUser() {
|
||||
const userData = {
|
||||
name: document.getElementById("createName").value,
|
||||
username: document.getElementById("createUsername").value,
|
||||
email: document.getElementById("createEmail").value,
|
||||
password: document.getElementById("createPassword").value,
|
||||
role: document.getElementById("createRole").value,
|
||||
isactive: true,
|
||||
passwordneverexpires: false,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/admin/users", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(userData),
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log("Create User:", data);
|
||||
showResult("result3", data);
|
||||
|
||||
// Store the created user ID for other tests
|
||||
if (data.success && data.user) {
|
||||
document.getElementById("getUserId").value = data.user.id;
|
||||
document.getElementById("updateUserId").value = data.user.id;
|
||||
document.getElementById("passwordUserId").value = data.user.id;
|
||||
document.getElementById("deleteUserId").value = data.user.id;
|
||||
}
|
||||
|
||||
// Generate new credentials for next test
|
||||
generateTestCredentials();
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
showResult("result3", error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Test 4: Update user
|
||||
async function testUpdateUser() {
|
||||
const userId = document.getElementById("updateUserId").value;
|
||||
if (!userId) {
|
||||
alert("Please enter a user ID");
|
||||
return;
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
name: document.getElementById("updateName").value,
|
||||
role: document.getElementById("updateRole").value,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${userId}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(updateData),
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log("Update User:", data);
|
||||
showResult("result4", data);
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
showResult("result4", error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Test 5: Change password
|
||||
async function testChangePassword() {
|
||||
const userId = document.getElementById("passwordUserId").value;
|
||||
const password = document.getElementById("newPassword").value;
|
||||
|
||||
if (!userId) {
|
||||
alert("Please enter a user ID");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${userId}/password`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ password: password }),
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log("Change Password:", data);
|
||||
showResult("result5", data);
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
showResult("result5", error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Test 6: Delete user
|
||||
async function testDeleteUser() {
|
||||
const userId = document.getElementById("deleteUserId").value;
|
||||
if (!userId) {
|
||||
alert("Please enter a user ID");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm("Are you sure you want to delete this user?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${userId}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log("Delete User:", data);
|
||||
showResult("result6", data);
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
showResult("result6", error.message, true);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -58,6 +58,176 @@
|
||||
border-color: #667eea;
|
||||
background: #f0f3ff;
|
||||
}
|
||||
|
||||
/* Custom Notification Styles */
|
||||
#notificationContainer {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 9999;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.notification {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
background: white;
|
||||
border-left: 4px solid;
|
||||
animation: slideIn 0.3s ease-out, fadeOut 0.3s ease-in 2.7s;
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
min-width: 320px;
|
||||
}
|
||||
|
||||
.notification.removing {
|
||||
animation: slideOut 0.3s ease-in;
|
||||
}
|
||||
|
||||
.notification-success {
|
||||
border-left-color: #10b981;
|
||||
background: linear-gradient(to right, #f0fdf4 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
.notification-error {
|
||||
border-left-color: #ef4444;
|
||||
background: linear-gradient(to right, #fef2f2 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
.notification-icon {
|
||||
font-size: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.notification-success .notification-icon {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.notification-error .notification-icon {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.notification-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.notification-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.notification-message {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.notification-close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 20px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.notification-close:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(400px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideOut {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: translateX(400px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
/* Loading Spinner Overlay */
|
||||
.loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9998;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.loading-spinner-container {
|
||||
background: white;
|
||||
padding: 30px 40px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-spinner-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #667eea;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 15px;
|
||||
}
|
||||
|
||||
.loading-spinner-text {
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -65,44 +235,44 @@
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html"
|
||||
<a href="/admin/dashboard"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html"
|
||||
<a href="/admin/homepage"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products.html"><i class="bi bi-box"></i> Products</a>
|
||||
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html"
|
||||
<a href="/admin/portfolio"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog.html"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html"
|
||||
<a href="/admin/pages"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html"
|
||||
<a href="/admin/media-library"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html"><i class="bi bi-list"></i> Menu</a>
|
||||
<a href="/admin/menu"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings.html"><i class="bi bi-gear"></i> Settings</a>
|
||||
<a href="/admin/settings"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users.html" class="active"
|
||||
<a href="/admin/users" class="active"
|
||||
><i class="bi bi-people"></i> Users</a
|
||||
>
|
||||
</li>
|
||||
@@ -382,6 +552,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom Notification Container -->
|
||||
<div id="notificationContainer"></div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/admin/js/auth.js"></script>
|
||||
<script src="/admin/js/users.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user