updateweb
This commit is contained in:
242
website/admin/blog.html
Normal file
242
website/admin/blog.html
Normal file
@@ -0,0 +1,242 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Blog Management - Sky Art Shop</title>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="/admin/css/admin-style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products.html"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog.html" class="active"
|
||||
><i class="bi bi-newspaper"></i> Blog</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings.html"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users.html"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="top-bar">
|
||||
<div>
|
||||
<h3>Blog Management</h3>
|
||||
<p class="mb-0 text-muted">Create and manage blog posts</p>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn-logout" onclick="logout()">
|
||||
<i class="bi bi-box-arrow-right"></i> Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions-bar">
|
||||
<button class="btn btn-primary" onclick="showCreatePost()">
|
||||
<i class="bi bi-plus-circle"></i> Create Blog Post
|
||||
</button>
|
||||
<div class="search-box">
|
||||
<i class="bi bi-search"></i>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search posts..."
|
||||
id="searchInput"
|
||||
oninput="filterPosts()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Title</th>
|
||||
<th>Slug</th>
|
||||
<th>Excerpt</th>
|
||||
<th>Status</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="postsTableBody">
|
||||
<tr>
|
||||
<td colspan="7" class="text-center">
|
||||
<div class="loading-spinner"></div>
|
||||
Loading posts...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="postModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalTitle">Create Blog Post</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="postForm">
|
||||
<input type="hidden" id="postId" />
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="postTitle" class="form-label">Title *</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="postTitle"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="postSlug" class="form-label">Slug *</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="postSlug"
|
||||
required
|
||||
/>
|
||||
<small class="text-muted"
|
||||
>URL-friendly version (auto-generated from title)</small
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="postExcerpt" class="form-label">Excerpt</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="postExcerpt"
|
||||
rows="2"
|
||||
></textarea>
|
||||
<small class="text-muted">Brief summary for listings</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="postContent" class="form-label">Content *</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="postContent"
|
||||
rows="10"
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="postImage" class="form-label">Featured Image</label>
|
||||
<input
|
||||
type="file"
|
||||
class="form-control"
|
||||
id="postImage"
|
||||
accept="image/*"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="postMetaTitle" class="form-label"
|
||||
>Meta Title (SEO)</label
|
||||
>
|
||||
<input type="text" class="form-control" id="postMetaTitle" />
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="postMetaDescription" class="form-label"
|
||||
>Meta Description (SEO)</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="postMetaDescription"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="postPublished"
|
||||
/>
|
||||
<label class="form-check-label" for="postPublished">
|
||||
Published (visible on website)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="savePost()">
|
||||
<i class="bi bi-save"></i> Save Post
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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/blog.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
442
website/admin/css/admin-style.css
Normal file
442
website/admin/css/admin-style.css
Normal file
@@ -0,0 +1,442 @@
|
||||
:root {
|
||||
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
--sidebar-width: 250px;
|
||||
--transition-speed: 0.3s;
|
||||
--primary-color: #667eea;
|
||||
--danger-color: #dc3545;
|
||||
--success-color: #28a745;
|
||||
--warning-color: #ffc107;
|
||||
--info-color: #17a2b8;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Sidebar Styles */
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
width: var(--sidebar-width);
|
||||
background: var(--primary-gradient);
|
||||
padding: 20px;
|
||||
color: white;
|
||||
overflow-y: auto;
|
||||
transition: all var(--transition-speed) ease;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.sidebar-brand {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sidebar-menu li {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.sidebar-menu a {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 15px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.25s ease;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.sidebar-menu a:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: white;
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.sidebar-menu a.active {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sidebar-menu i {
|
||||
margin-right: 12px;
|
||||
font-size: 1.2rem;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.main-content {
|
||||
margin-left: var(--sidebar-width);
|
||||
padding: 30px;
|
||||
min-height: 100vh;
|
||||
transition: margin-left var(--transition-speed) ease;
|
||||
}
|
||||
|
||||
/* Top Bar */
|
||||
.top-bar {
|
||||
background: white;
|
||||
padding: 25px 30px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
animation: slideDown 0.4s ease;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.top-bar h3 {
|
||||
margin: 0;
|
||||
font-size: 1.75rem;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.top-bar p {
|
||||
margin: 5px 0 0 0;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
/* Actions Bar */
|
||||
.actions-bar {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.search-box i {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
padding: 10px 15px 10px 45px;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.search-box input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
.table-responsive {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.table {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.table thead {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.table thead th {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
border-bottom: 2px solid #e9ecef;
|
||||
padding: 15px;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.table tbody td {
|
||||
padding: 15px;
|
||||
vertical-align: middle;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.table tbody tr {
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.table tbody tr:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary-gradient);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: var(--success-color);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: var(--danger-color);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: var(--warning-color);
|
||||
border: none;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background: var(--info-color);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-logout {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-logout:hover {
|
||||
background: #c82333;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.3);
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 6px 12px;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* Badges */
|
||||
.badge {
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.badge-danger {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.badge-info {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
/* Loading Animation */
|
||||
.loading-spinner {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid rgba(102, 126, 234, 0.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: #667eea;
|
||||
animation: spin 0.8s linear infinite;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Form Controls */
|
||||
.form-control:focus {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
|
||||
}
|
||||
|
||||
.form-check-input:checked {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Modal Enhancements */
|
||||
.modal-content {
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
background: var(--primary-gradient);
|
||||
color: white;
|
||||
border-radius: 12px 12px 0 0;
|
||||
padding: 20px 25px;
|
||||
}
|
||||
|
||||
.modal-header .btn-close {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 20px 25px;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.sidebar.active {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.actions-bar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Utility Classes */
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mt-3 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.mb-3 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.p-3 {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* Section Headings */
|
||||
.section-heading {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: #2c3e50;
|
||||
margin: 40px 0 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.section-heading::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
height: 2px;
|
||||
background: linear-gradient(to right, #667eea, transparent);
|
||||
}
|
||||
@@ -13,126 +13,366 @@
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
/>
|
||||
<style>
|
||||
:root {
|
||||
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
--sidebar-width: 250px;
|
||||
--transition-speed: 0.3s;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Sidebar Styles */
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
width: 250px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
width: var(--sidebar-width);
|
||||
background: var(--primary-gradient);
|
||||
padding: 20px;
|
||||
color: white;
|
||||
overflow-y: auto;
|
||||
transition: all var(--transition-speed) ease;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.sidebar-brand {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sidebar-menu li {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.sidebar-menu a {
|
||||
color: white;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 15px;
|
||||
border-radius: 8px;
|
||||
transition: background 0.3s;
|
||||
transition: all 0.25s ease;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.sidebar-menu a:hover,
|
||||
|
||||
.sidebar-menu a:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: white;
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.sidebar-menu a.active {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sidebar-menu i {
|
||||
margin-right: 10px;
|
||||
margin-right: 12px;
|
||||
font-size: 1.2rem;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.main-content {
|
||||
margin-left: 250px;
|
||||
margin-left: var(--sidebar-width);
|
||||
padding: 30px;
|
||||
min-height: 100vh;
|
||||
transition: margin-left var(--transition-speed) ease;
|
||||
}
|
||||
|
||||
/* Top Bar */
|
||||
.top-bar {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
padding: 25px 30px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
animation: slideDown 0.4s ease;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.top-bar h3 {
|
||||
margin: 0;
|
||||
font-size: 1.75rem;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.top-bar p {
|
||||
margin: 5px 0 0 0;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
/* Live Stats Cards - Enhanced */
|
||||
.stat-card {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
border: 2px solid transparent;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stat-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: var(--primary-gradient);
|
||||
transform: scaleX(0);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||
transform: translateY(-8px) scale(1.02);
|
||||
box-shadow: 0 12px 24px rgba(102, 126, 234, 0.3);
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.stat-card:hover::before {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.stat-card:active {
|
||||
transform: translateY(-4px) scale(1.01);
|
||||
}
|
||||
|
||||
.stat-card h6 {
|
||||
color: #6c757d;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 15px;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.stat-card h2 {
|
||||
color: #2c3e50;
|
||||
margin: 0;
|
||||
margin: 10px 0;
|
||||
font-weight: 700;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.stat-card .stat-icon {
|
||||
font-size: 2.5rem;
|
||||
opacity: 0.2;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
.stat-link {
|
||||
color: #667eea;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 10px;
|
||||
display: inline-block;
|
||||
margin-top: 15px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.stat-link i {
|
||||
margin-left: 5px;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover .stat-link i {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
/* Quick Action Cards - Enhanced */
|
||||
.action-card {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 12px;
|
||||
padding: 35px 25px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
text-align: center;
|
||||
transition: transform 0.2s;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.action-card::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: var(--primary-gradient);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.action-card:hover {
|
||||
transform: translateY(-5px);
|
||||
transform: translateY(-8px) scale(1.03);
|
||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.action-card:hover::after {
|
||||
opacity: 0.05;
|
||||
}
|
||||
|
||||
.action-card * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.action-card i {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 15px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.action-card:hover i {
|
||||
transform: scale(1.1) rotate(5deg);
|
||||
}
|
||||
|
||||
.action-card h6 {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn-view-site {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
background: var(--primary-gradient);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 25px;
|
||||
padding: 12px 28px;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.btn-view-site:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-logout {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-logout:hover {
|
||||
background: #c82333;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.3);
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.sidebar.active {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Loading Animation */
|
||||
.loading {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid rgba(102, 126, 234, 0.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: #667eea;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Section Headings */
|
||||
.section-heading {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: #2c3e50;
|
||||
margin: 40px 0 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.section-heading::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
height: 2px;
|
||||
background: linear-gradient(to right, #667eea, transparent);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -165,6 +405,11 @@
|
||||
<li>
|
||||
<a href="/admin/pages.html"><i class="bi bi-file-text"></i> Pages</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
@@ -199,36 +444,50 @@
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-4">
|
||||
<a href="/admin/products.html" class="stat-card">
|
||||
<i class="bi bi-box stat-icon"></i>
|
||||
<h6>Total Products</h6>
|
||||
<h2 id="productCount">-</h2>
|
||||
<span class="stat-link">Manage →</span>
|
||||
<h2 id="productCount"><span class="loading"></span></h2>
|
||||
<span class="stat-link"
|
||||
>Manage <i class="bi bi-arrow-right"></i
|
||||
></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-4">
|
||||
<a href="/admin/portfolio.html" class="stat-card">
|
||||
<i class="bi bi-easel stat-icon"></i>
|
||||
<h6>Portfolio Projects</h6>
|
||||
<h2 id="projectCount">-</h2>
|
||||
<span class="stat-link">Manage →</span>
|
||||
<h2 id="projectCount"><span class="loading"></span></h2>
|
||||
<span class="stat-link"
|
||||
>Manage <i class="bi bi-arrow-right"></i
|
||||
></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-4">
|
||||
<a href="/admin/blog.html" class="stat-card">
|
||||
<i class="bi bi-newspaper stat-icon"></i>
|
||||
<h6>Blog Posts</h6>
|
||||
<h2 id="blogCount">-</h2>
|
||||
<span class="stat-link">Manage →</span>
|
||||
<h2 id="blogCount"><span class="loading"></span></h2>
|
||||
<span class="stat-link"
|
||||
>Manage <i class="bi bi-arrow-right"></i
|
||||
></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-4">
|
||||
<a href="/admin/pages.html" class="stat-card">
|
||||
<i class="bi bi-file-text stat-icon"></i>
|
||||
<h6>Custom Pages</h6>
|
||||
<h2 id="pageCount">-</h2>
|
||||
<span class="stat-link">Manage →</span>
|
||||
<h2 id="pageCount"><span class="loading"></span></h2>
|
||||
<span class="stat-link"
|
||||
>Manage <i class="bi bi-arrow-right"></i
|
||||
></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<h4 class="mt-5 mb-4">Quick Actions</h4>
|
||||
<h4 class="section-heading">
|
||||
<i class="bi bi-lightning-fill"></i> Quick Actions
|
||||
</h4>
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-4">
|
||||
<a href="/admin/homepage.html" class="text-decoration-none">
|
||||
@@ -244,7 +503,7 @@
|
||||
class="text-decoration-none"
|
||||
>
|
||||
<div class="action-card">
|
||||
<i class="bi bi-plus-circle text-primary"></i>
|
||||
<i class="bi bi-plus-circle-fill text-primary"></i>
|
||||
<h6>Add New Product</h6>
|
||||
</div>
|
||||
</a>
|
||||
@@ -252,7 +511,7 @@
|
||||
<div class="col-md-3 mb-4">
|
||||
<a href="/admin/blog.html?action=create" class="text-decoration-none">
|
||||
<div class="action-card">
|
||||
<i class="bi bi-plus-circle text-info"></i>
|
||||
<i class="bi bi-file-earmark-plus-fill text-info"></i>
|
||||
<h6>Create Blog Post</h6>
|
||||
</div>
|
||||
</a>
|
||||
@@ -263,7 +522,7 @@
|
||||
class="text-decoration-none"
|
||||
>
|
||||
<div class="action-card">
|
||||
<i class="bi bi-plus-circle text-warning"></i>
|
||||
<i class="bi bi-brush-fill text-warning"></i>
|
||||
<h6>Add Portfolio Project</h6>
|
||||
</div>
|
||||
</a>
|
||||
@@ -272,34 +531,14 @@
|
||||
</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>
|
||||
// Check authentication
|
||||
async function checkAuth() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/session");
|
||||
credentials: "include",
|
||||
if (!response.ok) {
|
||||
window.location.href = "/admin/login.html";
|
||||
return;
|
||||
}
|
||||
const data = await response.json();
|
||||
if (!data.authenticated) {
|
||||
window.location.href = "/admin/login.html";
|
||||
return;
|
||||
}
|
||||
document.getElementById("userName").textContent =
|
||||
data.user.name || data.user.email;
|
||||
loadDashboardStats();
|
||||
} catch (error) {
|
||||
window.location.href = "/admin/login.html";
|
||||
}
|
||||
}
|
||||
|
||||
// Load dashboard statistics
|
||||
async function loadDashboardStats() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/dashboard/stats");
|
||||
credentials: "include",
|
||||
const response = await fetch("/api/admin/dashboard/stats", {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
document.getElementById("productCount").textContent =
|
||||
@@ -308,27 +547,26 @@
|
||||
data.stats.projects;
|
||||
document.getElementById("blogCount").textContent = data.stats.blog;
|
||||
document.getElementById("pageCount").textContent = data.stats.pages;
|
||||
|
||||
// Update user name
|
||||
if (data.user && (data.user.name || data.user.email)) {
|
||||
document.getElementById("userName").textContent =
|
||||
data.user.name || data.user.email;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load stats:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Logout function
|
||||
async function logout() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/logout", { method: "POST" });
|
||||
credentials: "include",
|
||||
if (response.ok) {
|
||||
window.location.href = "/admin/login.html";
|
||||
// Initialize - check auth then load stats
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadDashboardStats();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Logout failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
checkAuth();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
465
website/admin/homepage.html
Normal file
465
website/admin/homepage.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>Homepage Editor - Sky Art Shop</title>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="/admin/css/admin-style.css" />
|
||||
<style>
|
||||
.section-builder {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
border: 2px solid #e9ecef;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.section-builder:hover {
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.section-builder.disabled {
|
||||
opacity: 0.6;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 2px solid #e9ecef;
|
||||
}
|
||||
|
||||
.section-header h5 {
|
||||
margin: 0;
|
||||
color: #2c3e50;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.section-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.image-preview {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
height: 200px;
|
||||
border: 2px dashed #ccc;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
overflow: hidden;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.image-preview img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.image-preview.empty {
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.alignment-selector {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.alignment-btn {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border: 2px solid #e9ecef;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.alignment-btn:hover {
|
||||
border-color: #667eea;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.alignment-btn.active {
|
||||
border-color: #667eea;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
z-index: 999;
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html" 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>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog.html"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings.html"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users.html"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="top-bar">
|
||||
<div>
|
||||
<h3>Homepage Editor</h3>
|
||||
<p class="mb-0 text-muted">Customize your homepage sections</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/index.html" target="_blank" class="btn btn-info me-2">
|
||||
<i class="bi bi-eye"></i> Preview
|
||||
</a>
|
||||
<button class="btn-logout" onclick="logout()">
|
||||
<i class="bi bi-box-arrow-right"></i> Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="sectionsContainer">
|
||||
<!-- Hero Section -->
|
||||
<div class="section-builder" id="heroSection">
|
||||
<div class="section-header">
|
||||
<h5><i class="bi bi-stars"></i> Hero Section</h5>
|
||||
<div class="section-controls">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="heroEnabled"
|
||||
checked
|
||||
onchange="toggleSection('hero')"
|
||||
/>
|
||||
<label class="form-check-label" for="heroEnabled"
|
||||
>Enabled</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-content">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Headline *</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="heroHeadline"
|
||||
placeholder="Welcome to Sky Art Shop"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Subheading</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="heroSubheading"
|
||||
placeholder="Your creative destination"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="heroDescription"
|
||||
rows="3"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">CTA Button Text</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="heroCtaText"
|
||||
placeholder="Shop Now"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">CTA Button Link</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="heroCtaLink"
|
||||
placeholder="/shop"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Background Image/Video</label>
|
||||
<input
|
||||
type="file"
|
||||
class="form-control"
|
||||
id="heroBackground"
|
||||
accept="image/*,video/*"
|
||||
onchange="previewImage('hero')"
|
||||
/>
|
||||
<div class="image-preview empty" id="heroPreview">
|
||||
<i class="bi bi-image" style="font-size: 3rem"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Layout</label>
|
||||
<div class="alignment-selector">
|
||||
<button
|
||||
class="alignment-btn active"
|
||||
onclick="setLayout('hero', 'text-left')"
|
||||
>
|
||||
<i class="bi bi-align-start"></i> Text Left
|
||||
</button>
|
||||
<button
|
||||
class="alignment-btn"
|
||||
onclick="setLayout('hero', 'text-center')"
|
||||
>
|
||||
<i class="bi bi-align-center"></i> Text Center
|
||||
</button>
|
||||
<button
|
||||
class="alignment-btn"
|
||||
onclick="setLayout('hero', 'text-right')"
|
||||
>
|
||||
<i class="bi bi-align-end"></i> Text Right
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Promotion Section -->
|
||||
<div class="section-builder" id="promotionSection">
|
||||
<div class="section-header">
|
||||
<h5><i class="bi bi-gift"></i> Promotion Section</h5>
|
||||
<div class="section-controls">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="promotionEnabled"
|
||||
checked
|
||||
onchange="toggleSection('promotion')"
|
||||
/>
|
||||
<label class="form-check-label" for="promotionEnabled"
|
||||
>Enabled</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-content">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Section Title</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="promotionTitle"
|
||||
placeholder="Special Offers"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="promotionDescription"
|
||||
rows="3"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Section Image</label>
|
||||
<input
|
||||
type="file"
|
||||
class="form-control"
|
||||
id="promotionImage"
|
||||
accept="image/*"
|
||||
onchange="previewImage('promotion')"
|
||||
/>
|
||||
<div class="image-preview empty" id="promotionPreview">
|
||||
<i class="bi bi-image" style="font-size: 3rem"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Image Position</label>
|
||||
<div class="alignment-selector">
|
||||
<button
|
||||
class="alignment-btn active"
|
||||
onclick="setImagePosition('promotion', 'left')"
|
||||
>
|
||||
<i class="bi bi-arrow-left"></i> Left
|
||||
</button>
|
||||
<button
|
||||
class="alignment-btn"
|
||||
onclick="setImagePosition('promotion', 'center')"
|
||||
>
|
||||
<i class="bi bi-arrow-down"></i> Center
|
||||
</button>
|
||||
<button
|
||||
class="alignment-btn"
|
||||
onclick="setImagePosition('promotion', 'right')"
|
||||
>
|
||||
<i class="bi bi-arrow-right"></i> Right
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Text Alignment</label>
|
||||
<div class="alignment-selector">
|
||||
<button
|
||||
class="alignment-btn active"
|
||||
onclick="setTextAlignment('promotion', 'left')"
|
||||
>
|
||||
<i class="bi bi-text-left"></i> Left
|
||||
</button>
|
||||
<button
|
||||
class="alignment-btn"
|
||||
onclick="setTextAlignment('promotion', 'center')"
|
||||
>
|
||||
<i class="bi bi-text-center"></i> Center
|
||||
</button>
|
||||
<button
|
||||
class="alignment-btn"
|
||||
onclick="setTextAlignment('promotion', 'right')"
|
||||
>
|
||||
<i class="bi bi-text-right"></i> Right
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Portfolio Showcase Section -->
|
||||
<div class="section-builder" id="portfolioSection">
|
||||
<div class="section-header">
|
||||
<h5><i class="bi bi-easel"></i> Portfolio Showcase</h5>
|
||||
<div class="section-controls">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="portfolioEnabled"
|
||||
checked
|
||||
onchange="toggleSection('portfolio')"
|
||||
/>
|
||||
<label class="form-check-label" for="portfolioEnabled"
|
||||
>Enabled</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-content">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Section Title</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="portfolioTitle"
|
||||
placeholder="Our Work"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="portfolioDescription"
|
||||
rows="3"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Number of Projects to Display</label>
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
id="portfolioCount"
|
||||
value="6"
|
||||
min="3"
|
||||
max="12"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="btn btn-lg btn-primary save-button"
|
||||
onclick="saveHomepage()"
|
||||
>
|
||||
<i class="bi bi-save"></i> Save All Changes
|
||||
</button>
|
||||
</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/homepage.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
104
website/admin/js/auth.js
Normal file
104
website/admin/js/auth.js
Normal file
@@ -0,0 +1,104 @@
|
||||
// Shared Authentication Utility for Admin Panel
|
||||
// Include this file in all admin pages to handle authentication
|
||||
|
||||
// Global authentication state
|
||||
window.adminAuth = {
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
};
|
||||
|
||||
// Check authentication and redirect if needed
|
||||
async function checkAuth() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/session", {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
redirectToLogin();
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (!data.authenticated) {
|
||||
redirectToLogin();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store user data
|
||||
window.adminAuth.user = data.user;
|
||||
window.adminAuth.isAuthenticated = true;
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Authentication check failed:", error);
|
||||
redirectToLogin();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect to login page
|
||||
function redirectToLogin() {
|
||||
if (window.location.pathname !== "/admin/login.html") {
|
||||
window.location.href = "/admin/login.html";
|
||||
}
|
||||
}
|
||||
|
||||
// Logout function
|
||||
async function logout() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
window.adminAuth.user = null;
|
||||
window.adminAuth.isAuthenticated = false;
|
||||
window.location.href = "/admin/login.html";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Logout failed:", error);
|
||||
window.location.href = "/admin/login.html";
|
||||
}
|
||||
}
|
||||
|
||||
// Show success notification
|
||||
function showSuccess(message) {
|
||||
const alert = document.createElement("div");
|
||||
alert.className =
|
||||
"alert alert-success alert-dismissible fade show position-fixed";
|
||||
alert.style.cssText =
|
||||
"top: 20px; right: 20px; z-index: 9999; min-width: 300px;";
|
||||
alert.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
document.body.appendChild(alert);
|
||||
setTimeout(() => alert.remove(), 5000);
|
||||
}
|
||||
|
||||
// Show error notification
|
||||
function showError(message) {
|
||||
const alert = document.createElement("div");
|
||||
alert.className =
|
||||
"alert alert-danger alert-dismissible fade show position-fixed";
|
||||
alert.style.cssText =
|
||||
"top: 20px; right: 20px; z-index: 9999; min-width: 300px;";
|
||||
alert.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
document.body.appendChild(alert);
|
||||
setTimeout(() => alert.remove(), 5000);
|
||||
}
|
||||
|
||||
// Auto-check authentication when this script loads
|
||||
// Only run if we're not on the login page
|
||||
if (window.location.pathname !== "/admin/login.html") {
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
checkAuth();
|
||||
});
|
||||
}
|
||||
233
website/admin/js/blog.js
Normal file
233
website/admin/js/blog.js
Normal file
@@ -0,0 +1,233 @@
|
||||
// Blog Management JavaScript
|
||||
|
||||
let postsData = [];
|
||||
let postModal;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
postModal = new bootstrap.Modal(document.getElementById("postModal"));
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadPosts();
|
||||
}
|
||||
});
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get("action") === "create") {
|
||||
showCreatePost();
|
||||
}
|
||||
|
||||
// Auto-generate slug from title
|
||||
document.getElementById("postTitle")?.addEventListener("input", function () {
|
||||
if (!document.getElementById("postId").value) {
|
||||
document.getElementById("postSlug").value = slugify(this.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function loadPosts() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/blog", { credentials: "include" });
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
postsData = data.posts;
|
||||
renderPosts(postsData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load posts:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function renderPosts(posts) {
|
||||
const tbody = document.getElementById("postsTableBody");
|
||||
if (posts.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr><td colspan="7" class="text-center p-4">
|
||||
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
|
||||
<p class="mt-3 text-muted">No blog posts found</p>
|
||||
<button class="btn btn-primary" onclick="showCreatePost()">
|
||||
<i class="bi bi-plus-circle"></i> Create Your First Post
|
||||
</button>
|
||||
</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = posts
|
||||
.map(
|
||||
(p) => `
|
||||
<tr>
|
||||
<td>${p.id}</td>
|
||||
<td><strong>${escapeHtml(p.title)}</strong></td>
|
||||
<td><code>${escapeHtml(p.slug)}</code></td>
|
||||
<td>${escapeHtml((p.excerpt || "").substring(0, 40))}...</td>
|
||||
<td><span class="badge ${
|
||||
p.ispublished ? "badge-success" : "badge-warning"
|
||||
}">
|
||||
${p.ispublished ? "Published" : "Draft"}</span></td>
|
||||
<td>${formatDate(p.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editPost(${p.id})">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deletePost(${
|
||||
p.id
|
||||
}, '${escapeHtml(p.title)}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
function filterPosts() {
|
||||
const searchTerm = document.getElementById("searchInput").value.toLowerCase();
|
||||
const filtered = postsData.filter(
|
||||
(p) =>
|
||||
p.title.toLowerCase().includes(searchTerm) ||
|
||||
p.slug.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
renderPosts(filtered);
|
||||
}
|
||||
|
||||
function showCreatePost() {
|
||||
document.getElementById("modalTitle").textContent = "Create Blog Post";
|
||||
document.getElementById("postForm").reset();
|
||||
document.getElementById("postId").value = "";
|
||||
document.getElementById("postPublished").checked = false;
|
||||
postModal.show();
|
||||
}
|
||||
|
||||
async function editPost(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/blog/${id}`, {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
const post = data.post;
|
||||
document.getElementById("modalTitle").textContent = "Edit Blog Post";
|
||||
document.getElementById("postId").value = post.id;
|
||||
document.getElementById("postTitle").value = post.title;
|
||||
document.getElementById("postSlug").value = post.slug;
|
||||
document.getElementById("postExcerpt").value = post.excerpt || "";
|
||||
document.getElementById("postContent").value = post.content || "";
|
||||
document.getElementById("postMetaTitle").value = post.metatitle || "";
|
||||
document.getElementById("postMetaDescription").value =
|
||||
post.metadescription || "";
|
||||
document.getElementById("postPublished").checked = post.ispublished;
|
||||
postModal.show();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load post:", error);
|
||||
showError("Failed to load post details");
|
||||
}
|
||||
}
|
||||
|
||||
async function savePost() {
|
||||
const id = document.getElementById("postId").value;
|
||||
const formData = {
|
||||
title: document.getElementById("postTitle").value,
|
||||
slug: document.getElementById("postSlug").value,
|
||||
excerpt: document.getElementById("postExcerpt").value,
|
||||
content: document.getElementById("postContent").value,
|
||||
metatitle: document.getElementById("postMetaTitle").value,
|
||||
metadescription: document.getElementById("postMetaDescription").value,
|
||||
ispublished: document.getElementById("postPublished").checked,
|
||||
};
|
||||
|
||||
if (!formData.title || !formData.slug || !formData.content) {
|
||||
showError("Please fill in all required fields");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = id ? `/api/admin/blog/${id}` : "/api/admin/blog";
|
||||
const method = id ? "PUT" : "POST";
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess(
|
||||
id ? "Post updated successfully" : "Post created successfully"
|
||||
);
|
||||
postModal.hide();
|
||||
loadPosts();
|
||||
} else {
|
||||
showError(data.message || "Failed to save post");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save post:", error);
|
||||
showError("Failed to save post");
|
||||
}
|
||||
}
|
||||
|
||||
async function deletePost(id, title) {
|
||||
if (!confirm(`Are you sure you want to delete "${title}"?`)) return;
|
||||
try {
|
||||
const response = await fetch(`/api/admin/blog/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess("Post deleted successfully");
|
||||
loadPosts();
|
||||
} else {
|
||||
showError(data.message || "Failed to delete post");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete post:", error);
|
||||
showError("Failed to delete post");
|
||||
}
|
||||
}
|
||||
|
||||
function slugify(text) {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/[^\w\s-]/g, "")
|
||||
.replace(/[\s_-]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
if (response.ok) window.location.href = "/admin/login.html";
|
||||
} catch (error) {
|
||||
console.error("Logout failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
};
|
||||
return text.replace(/[&<>"']/g, (m) => map[m]);
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
return new Date(dateString).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
alert(message);
|
||||
}
|
||||
function showError(message) {
|
||||
alert("Error: " + message);
|
||||
}
|
||||
196
website/admin/js/homepage.js
Normal file
196
website/admin/js/homepage.js
Normal file
@@ -0,0 +1,196 @@
|
||||
// Homepage Editor JavaScript
|
||||
|
||||
let homepageData = {};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadHomepageSettings();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function loadHomepageSettings() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/homepage/settings", {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
homepageData = data.settings || {};
|
||||
populateFields();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load homepage settings:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function populateFields() {
|
||||
// Hero Section
|
||||
if (homepageData.hero) {
|
||||
document.getElementById("heroEnabled").checked =
|
||||
homepageData.hero.enabled !== false;
|
||||
document.getElementById("heroHeadline").value =
|
||||
homepageData.hero.headline || "";
|
||||
document.getElementById("heroSubheading").value =
|
||||
homepageData.hero.subheading || "";
|
||||
document.getElementById("heroDescription").value =
|
||||
homepageData.hero.description || "";
|
||||
document.getElementById("heroCtaText").value =
|
||||
homepageData.hero.ctaText || "";
|
||||
document.getElementById("heroCtaLink").value =
|
||||
homepageData.hero.ctaLink || "";
|
||||
toggleSection("hero");
|
||||
}
|
||||
|
||||
// Promotion Section
|
||||
if (homepageData.promotion) {
|
||||
document.getElementById("promotionEnabled").checked =
|
||||
homepageData.promotion.enabled !== false;
|
||||
document.getElementById("promotionTitle").value =
|
||||
homepageData.promotion.title || "";
|
||||
document.getElementById("promotionDescription").value =
|
||||
homepageData.promotion.description || "";
|
||||
toggleSection("promotion");
|
||||
}
|
||||
|
||||
// Portfolio Section
|
||||
if (homepageData.portfolio) {
|
||||
document.getElementById("portfolioEnabled").checked =
|
||||
homepageData.portfolio.enabled !== false;
|
||||
document.getElementById("portfolioTitle").value =
|
||||
homepageData.portfolio.title || "";
|
||||
document.getElementById("portfolioDescription").value =
|
||||
homepageData.portfolio.description || "";
|
||||
document.getElementById("portfolioCount").value =
|
||||
homepageData.portfolio.count || 6;
|
||||
toggleSection("portfolio");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSection(sectionName) {
|
||||
const enabled = document.getElementById(`${sectionName}Enabled`).checked;
|
||||
const section = document.getElementById(`${sectionName}Section`);
|
||||
const content = section.querySelector(".section-content");
|
||||
|
||||
if (enabled) {
|
||||
section.classList.remove("disabled");
|
||||
content
|
||||
.querySelectorAll("input, textarea, button, select")
|
||||
.forEach((el) => {
|
||||
el.disabled = false;
|
||||
});
|
||||
} else {
|
||||
section.classList.add("disabled");
|
||||
content
|
||||
.querySelectorAll("input, textarea, button, select")
|
||||
.forEach((el) => {
|
||||
el.disabled = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function previewImage(sectionName) {
|
||||
const fileInput =
|
||||
document.getElementById(`${sectionName}Background`) ||
|
||||
document.getElementById(`${sectionName}Image`);
|
||||
const preview = document.getElementById(`${sectionName}Preview`);
|
||||
|
||||
if (fileInput.files && fileInput.files[0]) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
preview.classList.remove("empty");
|
||||
preview.innerHTML = `<img src="${e.target.result}" alt="Preview" />`;
|
||||
};
|
||||
reader.readAsDataURL(fileInput.files[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function setLayout(sectionName, layout) {
|
||||
const buttons = document.querySelectorAll(
|
||||
`#${sectionName}Section .alignment-btn`
|
||||
);
|
||||
buttons.forEach((btn) => btn.classList.remove("active"));
|
||||
event.target.closest(".alignment-btn").classList.add("active");
|
||||
}
|
||||
|
||||
function setImagePosition(sectionName, position) {
|
||||
const buttons = event.target
|
||||
.closest(".alignment-selector")
|
||||
.querySelectorAll(".alignment-btn");
|
||||
buttons.forEach((btn) => btn.classList.remove("active"));
|
||||
event.target.closest(".alignment-btn").classList.add("active");
|
||||
}
|
||||
|
||||
function setTextAlignment(sectionName, alignment) {
|
||||
const buttons = event.target
|
||||
.closest(".alignment-selector")
|
||||
.querySelectorAll(".alignment-btn");
|
||||
buttons.forEach((btn) => btn.classList.remove("active"));
|
||||
event.target.closest(".alignment-btn").classList.add("active");
|
||||
}
|
||||
|
||||
async function saveHomepage() {
|
||||
const settings = {
|
||||
hero: {
|
||||
enabled: document.getElementById("heroEnabled").checked,
|
||||
headline: document.getElementById("heroHeadline").value,
|
||||
subheading: document.getElementById("heroSubheading").value,
|
||||
description: document.getElementById("heroDescription").value,
|
||||
ctaText: document.getElementById("heroCtaText").value,
|
||||
ctaLink: document.getElementById("heroCtaLink").value,
|
||||
},
|
||||
promotion: {
|
||||
enabled: document.getElementById("promotionEnabled").checked,
|
||||
title: document.getElementById("promotionTitle").value,
|
||||
description: document.getElementById("promotionDescription").value,
|
||||
},
|
||||
portfolio: {
|
||||
enabled: document.getElementById("portfolioEnabled").checked,
|
||||
title: document.getElementById("portfolioTitle").value,
|
||||
description: document.getElementById("portfolioDescription").value,
|
||||
count: parseInt(document.getElementById("portfolioCount").value) || 6,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/admin/homepage/settings", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(settings),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess(
|
||||
"Homepage settings saved successfully! Changes are now live."
|
||||
);
|
||||
} else {
|
||||
showError(data.message || "Failed to save homepage settings");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save homepage:", error);
|
||||
showError("Failed to save homepage settings");
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
if (response.ok) window.location.href = "/admin/login.html";
|
||||
} catch (error) {
|
||||
console.error("Logout failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
alert(message);
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
alert("Error: " + message);
|
||||
}
|
||||
232
website/admin/js/pages.js
Normal file
232
website/admin/js/pages.js
Normal file
@@ -0,0 +1,232 @@
|
||||
// Pages Management JavaScript
|
||||
|
||||
let pagesData = [];
|
||||
let pageModal;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
pageModal = new bootstrap.Modal(document.getElementById("pageModal"));
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadPages();
|
||||
}
|
||||
});
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get("action") === "create") {
|
||||
showCreatePage();
|
||||
}
|
||||
|
||||
// Auto-generate slug from title
|
||||
document.getElementById("pageTitle")?.addEventListener("input", function () {
|
||||
if (!document.getElementById("pageId").value) {
|
||||
document.getElementById("pageSlug").value = slugify(this.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function loadPages() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/pages", {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
pagesData = data.pages;
|
||||
renderPages(pagesData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load pages:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function renderPages(pages) {
|
||||
const tbody = document.getElementById("pagesTableBody");
|
||||
if (pages.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr><td colspan="6" class="text-center p-4">
|
||||
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
|
||||
<p class="mt-3 text-muted">No custom pages found</p>
|
||||
<button class="btn btn-primary" onclick="showCreatePage()">
|
||||
<i class="bi bi-plus-circle"></i> Create Your First Page
|
||||
</button>
|
||||
</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = pages
|
||||
.map(
|
||||
(p) => `
|
||||
<tr>
|
||||
<td>${p.id}</td>
|
||||
<td><strong>${escapeHtml(p.title)}</strong></td>
|
||||
<td><code>${escapeHtml(p.slug)}</code></td>
|
||||
<td><span class="badge ${
|
||||
p.ispublished ? "badge-success" : "badge-warning"
|
||||
}">
|
||||
${p.ispublished ? "Published" : "Draft"}</span></td>
|
||||
<td>${formatDate(p.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editPage(${p.id})">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deletePage(${
|
||||
p.id
|
||||
}, '${escapeHtml(p.title)}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
function filterPages() {
|
||||
const searchTerm = document.getElementById("searchInput").value.toLowerCase();
|
||||
const filtered = pagesData.filter(
|
||||
(p) =>
|
||||
p.title.toLowerCase().includes(searchTerm) ||
|
||||
p.slug.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
renderPages(filtered);
|
||||
}
|
||||
|
||||
function showCreatePage() {
|
||||
document.getElementById("modalTitle").textContent = "Create Custom Page";
|
||||
document.getElementById("pageForm").reset();
|
||||
document.getElementById("pageId").value = "";
|
||||
document.getElementById("pagePublished").checked = true;
|
||||
pageModal.show();
|
||||
}
|
||||
|
||||
async function editPage(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/pages/${id}`, {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
const page = data.page;
|
||||
document.getElementById("modalTitle").textContent = "Edit Custom Page";
|
||||
document.getElementById("pageId").value = page.id;
|
||||
document.getElementById("pageTitle").value = page.title;
|
||||
document.getElementById("pageSlug").value = page.slug;
|
||||
document.getElementById("pageContent").value = page.content || "";
|
||||
document.getElementById("pageMetaTitle").value = page.metatitle || "";
|
||||
document.getElementById("pageMetaDescription").value =
|
||||
page.metadescription || "";
|
||||
document.getElementById("pagePublished").checked = page.ispublished;
|
||||
pageModal.show();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load page:", error);
|
||||
showError("Failed to load page details");
|
||||
}
|
||||
}
|
||||
|
||||
async function savePage() {
|
||||
const id = document.getElementById("pageId").value;
|
||||
const formData = {
|
||||
title: document.getElementById("pageTitle").value,
|
||||
slug: document.getElementById("pageSlug").value,
|
||||
content: document.getElementById("pageContent").value,
|
||||
metatitle: document.getElementById("pageMetaTitle").value,
|
||||
metadescription: document.getElementById("pageMetaDescription").value,
|
||||
ispublished: document.getElementById("pagePublished").checked,
|
||||
};
|
||||
|
||||
if (!formData.title || !formData.slug || !formData.content) {
|
||||
showError("Please fill in all required fields");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = id ? `/api/admin/pages/${id}` : "/api/admin/pages";
|
||||
const method = id ? "PUT" : "POST";
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess(
|
||||
id ? "Page updated successfully" : "Page created successfully"
|
||||
);
|
||||
pageModal.hide();
|
||||
loadPages();
|
||||
} else {
|
||||
showError(data.message || "Failed to save page");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save page:", error);
|
||||
showError("Failed to save page");
|
||||
}
|
||||
}
|
||||
|
||||
async function deletePage(id, title) {
|
||||
if (!confirm(`Are you sure you want to delete "${title}"?`)) return;
|
||||
try {
|
||||
const response = await fetch(`/api/admin/pages/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess("Page deleted successfully");
|
||||
loadPages();
|
||||
} else {
|
||||
showError(data.message || "Failed to delete page");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete page:", error);
|
||||
showError("Failed to delete page");
|
||||
}
|
||||
}
|
||||
|
||||
function slugify(text) {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/[^\w\s-]/g, "")
|
||||
.replace(/[\s_-]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
if (response.ok) window.location.href = "/admin/login.html";
|
||||
} catch (error) {
|
||||
console.error("Logout failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
};
|
||||
return text.replace(/[&<>"']/g, (m) => map[m]);
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
return new Date(dateString).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
alert(message);
|
||||
}
|
||||
function showError(message) {
|
||||
alert("Error: " + message);
|
||||
}
|
||||
213
website/admin/js/portfolio.js
Normal file
213
website/admin/js/portfolio.js
Normal file
@@ -0,0 +1,213 @@
|
||||
// Portfolio Management JavaScript
|
||||
|
||||
let projectsData = [];
|
||||
let projectModal;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
projectModal = new bootstrap.Modal(document.getElementById("projectModal"));
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadProjects();
|
||||
}
|
||||
});
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get("action") === "create") {
|
||||
showCreateProject();
|
||||
}
|
||||
});
|
||||
|
||||
async function loadProjects() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/portfolio/projects", {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
projectsData = data.projects;
|
||||
renderProjects(projectsData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load projects:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function renderProjects(projects) {
|
||||
const tbody = document.getElementById("projectsTableBody");
|
||||
if (projects.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr><td colspan="7" class="text-center p-4">
|
||||
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
|
||||
<p class="mt-3 text-muted">No projects found</p>
|
||||
<button class="btn btn-primary" onclick="showCreateProject()">
|
||||
<i class="bi bi-plus-circle"></i> Add Your First Project
|
||||
</button>
|
||||
</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = projects
|
||||
.map(
|
||||
(p) => `
|
||||
<tr>
|
||||
<td>${p.id}</td>
|
||||
<td><strong>${escapeHtml(p.title)}</strong></td>
|
||||
<td>${escapeHtml((p.description || "").substring(0, 50))}...</td>
|
||||
<td>${p.category || "-"}</td>
|
||||
<td><span class="badge ${p.isactive ? "badge-success" : "badge-danger"}">
|
||||
${p.isactive ? "Active" : "Inactive"}</span></td>
|
||||
<td>${formatDate(p.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editProject(${p.id})">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteProject(${
|
||||
p.id
|
||||
}, '${escapeHtml(p.title)}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
function filterProjects() {
|
||||
const searchTerm = document.getElementById("searchInput").value.toLowerCase();
|
||||
const filtered = projectsData.filter((p) =>
|
||||
p.title.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
renderProjects(filtered);
|
||||
}
|
||||
|
||||
function showCreateProject() {
|
||||
document.getElementById("modalTitle").textContent = "Add Portfolio Project";
|
||||
document.getElementById("projectForm").reset();
|
||||
document.getElementById("projectId").value = "";
|
||||
document.getElementById("projectActive").checked = true;
|
||||
projectModal.show();
|
||||
}
|
||||
|
||||
async function editProject(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/portfolio/projects/${id}`, {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
const project = data.project;
|
||||
document.getElementById("modalTitle").textContent =
|
||||
"Edit Portfolio Project";
|
||||
document.getElementById("projectId").value = project.id;
|
||||
document.getElementById("projectTitle").value = project.title;
|
||||
document.getElementById("projectDescription").value =
|
||||
project.description || "";
|
||||
document.getElementById("projectCategory").value = project.category || "";
|
||||
document.getElementById("projectActive").checked = project.isactive;
|
||||
projectModal.show();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load project:", error);
|
||||
showError("Failed to load project details");
|
||||
}
|
||||
}
|
||||
|
||||
async function saveProject() {
|
||||
const id = document.getElementById("projectId").value;
|
||||
const formData = {
|
||||
title: document.getElementById("projectTitle").value,
|
||||
description: document.getElementById("projectDescription").value,
|
||||
category: document.getElementById("projectCategory").value,
|
||||
isactive: document.getElementById("projectActive").checked,
|
||||
};
|
||||
|
||||
if (!formData.title || !formData.description) {
|
||||
showError("Please fill in all required fields");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = id
|
||||
? `/api/admin/portfolio/projects/${id}`
|
||||
: "/api/admin/portfolio/projects";
|
||||
const method = id ? "PUT" : "POST";
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess(
|
||||
id ? "Project updated successfully" : "Project created successfully"
|
||||
);
|
||||
projectModal.hide();
|
||||
loadProjects();
|
||||
} else {
|
||||
showError(data.message || "Failed to save project");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save project:", error);
|
||||
showError("Failed to save project");
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteProject(id, name) {
|
||||
if (!confirm(`Are you sure you want to delete "${name}"?`)) return;
|
||||
try {
|
||||
const response = await fetch(`/api/admin/portfolio/projects/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess("Project deleted successfully");
|
||||
loadProjects();
|
||||
} else {
|
||||
showError(data.message || "Failed to delete project");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete project:", error);
|
||||
showError("Failed to delete project");
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
if (response.ok) window.location.href = "/admin/login.html";
|
||||
} catch (error) {
|
||||
console.error("Logout failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
};
|
||||
return text.replace(/[&<>"']/g, (m) => map[m]);
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
return new Date(dateString).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
alert(message);
|
||||
}
|
||||
function showError(message) {
|
||||
alert("Error: " + message);
|
||||
}
|
||||
270
website/admin/js/products.js
Normal file
270
website/admin/js/products.js
Normal file
@@ -0,0 +1,270 @@
|
||||
// Products Management JavaScript
|
||||
|
||||
let productsData = [];
|
||||
let productModal;
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Initialize Bootstrap modal
|
||||
productModal = new bootstrap.Modal(document.getElementById("productModal"));
|
||||
|
||||
// Check authentication (from auth.js)
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadProducts();
|
||||
}
|
||||
});
|
||||
|
||||
// Check if we should open create modal
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get("action") === "create") {
|
||||
showCreateProduct();
|
||||
}
|
||||
});
|
||||
|
||||
// Load all products
|
||||
async function loadProducts() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/products", {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
productsData = data.products;
|
||||
renderProducts(productsData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load products:", error);
|
||||
showError("Failed to load products");
|
||||
}
|
||||
}
|
||||
|
||||
// Render products table
|
||||
function renderProducts(products) {
|
||||
const tbody = document.getElementById("productsTableBody");
|
||||
|
||||
if (products.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="8" class="text-center p-4">
|
||||
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
|
||||
<p class="mt-3 text-muted">No products found</p>
|
||||
<button class="btn btn-primary" onclick="showCreateProduct()">
|
||||
<i class="bi bi-plus-circle"></i> Add Your First Product
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = products
|
||||
.map(
|
||||
(product) => `
|
||||
<tr>
|
||||
<td>${product.id}</td>
|
||||
<td><strong>${escapeHtml(product.name)}</strong></td>
|
||||
<td>$${parseFloat(product.price).toFixed(2)}</td>
|
||||
<td>${product.stockquantity || 0}</td>
|
||||
<td>
|
||||
<span class="badge ${
|
||||
product.isactive ? "badge-success" : "badge-danger"
|
||||
}">
|
||||
${product.isactive ? "Active" : "Inactive"}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
${
|
||||
product.isbestseller
|
||||
? '<span class="badge badge-warning">⭐ Best Seller</span>'
|
||||
: "-"
|
||||
}
|
||||
</td>
|
||||
<td>${formatDate(product.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editProduct(${
|
||||
product.id
|
||||
})">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteProduct(${
|
||||
product.id
|
||||
}, '${escapeHtml(product.name)}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
// Filter products
|
||||
function filterProducts() {
|
||||
const searchTerm = document.getElementById("searchInput").value.toLowerCase();
|
||||
const filtered = productsData.filter((product) =>
|
||||
product.name.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
renderProducts(filtered);
|
||||
}
|
||||
|
||||
// Show create product modal
|
||||
function showCreateProduct() {
|
||||
document.getElementById("modalTitle").textContent = "Add New Product";
|
||||
document.getElementById("productForm").reset();
|
||||
document.getElementById("productId").value = "";
|
||||
document.getElementById("productActive").checked = true;
|
||||
productModal.show();
|
||||
}
|
||||
|
||||
// Edit product
|
||||
async function editProduct(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/products/${id}`, {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
const product = data.product;
|
||||
|
||||
document.getElementById("modalTitle").textContent = "Edit Product";
|
||||
document.getElementById("productId").value = product.id;
|
||||
document.getElementById("productName").value = product.name;
|
||||
document.getElementById("productDescription").value =
|
||||
product.description || "";
|
||||
document.getElementById("productPrice").value = product.price;
|
||||
document.getElementById("productStock").value =
|
||||
product.stockquantity || 0;
|
||||
document.getElementById("productCategory").value = product.category || "";
|
||||
document.getElementById("productActive").checked = product.isactive;
|
||||
document.getElementById("productBestSeller").checked =
|
||||
product.isbestseller || false;
|
||||
|
||||
productModal.show();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load product:", error);
|
||||
showError("Failed to load product details");
|
||||
}
|
||||
}
|
||||
|
||||
// Save product
|
||||
async function saveProduct() {
|
||||
const id = document.getElementById("productId").value;
|
||||
const formData = {
|
||||
name: document.getElementById("productName").value,
|
||||
description: document.getElementById("productDescription").value,
|
||||
price: parseFloat(document.getElementById("productPrice").value),
|
||||
stockquantity: parseInt(document.getElementById("productStock").value) || 0,
|
||||
category: document.getElementById("productCategory").value,
|
||||
isactive: document.getElementById("productActive").checked,
|
||||
isbestseller: document.getElementById("productBestSeller").checked,
|
||||
};
|
||||
|
||||
// Validation
|
||||
if (!formData.name || !formData.price) {
|
||||
showError("Please fill in all required fields");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = id ? `/api/admin/products/${id}` : "/api/admin/products";
|
||||
const method = id ? "PUT" : "POST";
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess(
|
||||
id ? "Product updated successfully" : "Product created successfully"
|
||||
);
|
||||
productModal.hide();
|
||||
loadProducts();
|
||||
} else {
|
||||
showError(data.message || "Failed to save product");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save product:", error);
|
||||
showError("Failed to save product");
|
||||
}
|
||||
}
|
||||
|
||||
// Delete product
|
||||
async function deleteProduct(id, name) {
|
||||
if (!confirm(`Are you sure you want to delete "${name}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/products/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess("Product deleted successfully");
|
||||
loadProducts();
|
||||
} else {
|
||||
showError(data.message || "Failed to delete product");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete product:", error);
|
||||
showError("Failed to delete product");
|
||||
}
|
||||
}
|
||||
|
||||
// Logout function
|
||||
async function logout() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
window.location.href = "/admin/login.html";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Logout failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
function escapeHtml(text) {
|
||||
const map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
};
|
||||
return text.replace(/[&<>"']/g, (m) => map[m]);
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
// Simple alert for now - can be replaced with toast notification
|
||||
alert(message);
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
alert("Error: " + message);
|
||||
}
|
||||
213
website/admin/js/settings.js
Normal file
213
website/admin/js/settings.js
Normal file
@@ -0,0 +1,213 @@
|
||||
// Settings Management JavaScript
|
||||
|
||||
let currentSettings = {};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadSettings();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/settings", {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
currentSettings = data.settings || {};
|
||||
populateSettings();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load settings:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function populateSettings() {
|
||||
// General Settings
|
||||
if (currentSettings.general) {
|
||||
document.getElementById("siteName").value =
|
||||
currentSettings.general.siteName || "";
|
||||
document.getElementById("siteTagline").value =
|
||||
currentSettings.general.siteTagline || "";
|
||||
document.getElementById("siteEmail").value =
|
||||
currentSettings.general.siteEmail || "";
|
||||
document.getElementById("sitePhone").value =
|
||||
currentSettings.general.sitePhone || "";
|
||||
document.getElementById("timezone").value =
|
||||
currentSettings.general.timezone || "UTC";
|
||||
}
|
||||
|
||||
// Homepage Settings
|
||||
if (currentSettings.homepage) {
|
||||
document.getElementById("showHero").checked =
|
||||
currentSettings.homepage.showHero !== false;
|
||||
document.getElementById("showPromotions").checked =
|
||||
currentSettings.homepage.showPromotions !== false;
|
||||
document.getElementById("showPortfolio").checked =
|
||||
currentSettings.homepage.showPortfolio !== false;
|
||||
document.getElementById("showBlog").checked =
|
||||
currentSettings.homepage.showBlog !== false;
|
||||
}
|
||||
|
||||
// Product Settings
|
||||
if (currentSettings.product) {
|
||||
document.getElementById("defaultProductStatus").value =
|
||||
currentSettings.product.defaultStatus || "active";
|
||||
document.getElementById("productsPerPage").value =
|
||||
currentSettings.product.perPage || 12;
|
||||
document.getElementById("bestSellerLogic").value =
|
||||
currentSettings.product.bestSellerLogic || "manual";
|
||||
document.getElementById("enableInventory").checked =
|
||||
currentSettings.product.enableInventory !== false;
|
||||
document.getElementById("showOutOfStock").checked =
|
||||
currentSettings.product.showOutOfStock !== false;
|
||||
}
|
||||
|
||||
// Security Settings
|
||||
if (currentSettings.security) {
|
||||
document.getElementById("passwordExpiration").value =
|
||||
currentSettings.security.passwordExpiration || 90;
|
||||
document.getElementById("sessionTimeout").value =
|
||||
currentSettings.security.sessionTimeout || 60;
|
||||
document.getElementById("loginAttempts").value =
|
||||
currentSettings.security.loginAttempts || 5;
|
||||
document.getElementById("requireStrongPassword").checked =
|
||||
currentSettings.security.requireStrongPassword !== false;
|
||||
document.getElementById("enableTwoFactor").checked =
|
||||
currentSettings.security.enableTwoFactor || false;
|
||||
}
|
||||
|
||||
// Appearance Settings
|
||||
if (currentSettings.appearance) {
|
||||
document.getElementById("accentColor").value =
|
||||
currentSettings.appearance.accentColor || "#667eea";
|
||||
updateColorPreview();
|
||||
}
|
||||
}
|
||||
|
||||
function previewLogo() {
|
||||
const fileInput = document.getElementById("siteLogo");
|
||||
const preview = document.getElementById("logoPreview");
|
||||
|
||||
if (fileInput.files && fileInput.files[0]) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
preview.innerHTML = `<img src="${e.target.result}" alt="Logo" />`;
|
||||
};
|
||||
reader.readAsDataURL(fileInput.files[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function previewFavicon() {
|
||||
const fileInput = document.getElementById("siteFavicon");
|
||||
const preview = document.getElementById("faviconPreview");
|
||||
|
||||
if (fileInput.files && fileInput.files[0]) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
preview.innerHTML = `<img src="${e.target.result}" alt="Favicon" />`;
|
||||
};
|
||||
reader.readAsDataURL(fileInput.files[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function selectLayout(layout) {
|
||||
document.querySelectorAll(".theme-selector .theme-option").forEach((el) => {
|
||||
el.classList.remove("active");
|
||||
});
|
||||
event.target.closest(".theme-option").classList.add("active");
|
||||
}
|
||||
|
||||
function selectTheme(theme) {
|
||||
document.querySelectorAll(".theme-selector .theme-option").forEach((el) => {
|
||||
el.classList.remove("active");
|
||||
});
|
||||
event.target.closest(".theme-option").classList.add("active");
|
||||
}
|
||||
|
||||
function updateColorPreview() {
|
||||
const color = document.getElementById("accentColor").value;
|
||||
document.getElementById("colorPreview").style.backgroundColor = color;
|
||||
document.getElementById("colorValue").textContent = color;
|
||||
}
|
||||
|
||||
async function saveSettings() {
|
||||
const settings = {
|
||||
general: {
|
||||
siteName: document.getElementById("siteName").value,
|
||||
siteTagline: document.getElementById("siteTagline").value,
|
||||
siteEmail: document.getElementById("siteEmail").value,
|
||||
sitePhone: document.getElementById("sitePhone").value,
|
||||
timezone: document.getElementById("timezone").value,
|
||||
},
|
||||
homepage: {
|
||||
showHero: document.getElementById("showHero").checked,
|
||||
showPromotions: document.getElementById("showPromotions").checked,
|
||||
showPortfolio: document.getElementById("showPortfolio").checked,
|
||||
showBlog: document.getElementById("showBlog").checked,
|
||||
},
|
||||
product: {
|
||||
defaultStatus: document.getElementById("defaultProductStatus").value,
|
||||
perPage: parseInt(document.getElementById("productsPerPage").value),
|
||||
bestSellerLogic: document.getElementById("bestSellerLogic").value,
|
||||
enableInventory: document.getElementById("enableInventory").checked,
|
||||
showOutOfStock: document.getElementById("showOutOfStock").checked,
|
||||
},
|
||||
security: {
|
||||
passwordExpiration: parseInt(
|
||||
document.getElementById("passwordExpiration").value
|
||||
),
|
||||
sessionTimeout: parseInt(document.getElementById("sessionTimeout").value),
|
||||
loginAttempts: parseInt(document.getElementById("loginAttempts").value),
|
||||
requireStrongPassword: document.getElementById("requireStrongPassword")
|
||||
.checked,
|
||||
enableTwoFactor: document.getElementById("enableTwoFactor").checked,
|
||||
},
|
||||
appearance: {
|
||||
accentColor: document.getElementById("accentColor").value,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/admin/settings", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(settings),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess("Settings saved successfully!");
|
||||
currentSettings = settings;
|
||||
} else {
|
||||
showError(data.message || "Failed to save settings");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save settings:", error);
|
||||
showError("Failed to save settings");
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
if (response.ok) window.location.href = "/admin/login.html";
|
||||
} catch (error) {
|
||||
console.error("Logout failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
alert(message);
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
alert("Error: " + message);
|
||||
}
|
||||
352
website/admin/js/users.js
Normal file
352
website/admin/js/users.js
Normal file
@@ -0,0 +1,352 @@
|
||||
// User Management JavaScript
|
||||
|
||||
let usersData = [];
|
||||
let userModal;
|
||||
let passwordModal;
|
||||
|
||||
const rolePermissions = {
|
||||
Cashier: ["View Products", "Process Orders", "View Customers"],
|
||||
Accountant: [
|
||||
"View Products",
|
||||
"View Orders",
|
||||
"View Reports",
|
||||
"View Financial Data",
|
||||
],
|
||||
Admin: [
|
||||
"Manage Products",
|
||||
"Manage Portfolio",
|
||||
"Manage Blog",
|
||||
"Manage Pages",
|
||||
"Manage Users",
|
||||
"View Reports",
|
||||
],
|
||||
MasterAdmin: [
|
||||
"Full System Access",
|
||||
"Manage Settings",
|
||||
"Manage Users",
|
||||
"Manage All Content",
|
||||
"View Logs",
|
||||
"System Configuration",
|
||||
],
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
userModal = new bootstrap.Modal(document.getElementById("userModal"));
|
||||
passwordModal = new bootstrap.Modal(document.getElementById("passwordModal"));
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadUsers();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function loadUsers() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/users", {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
usersData = data.users;
|
||||
renderUsers(usersData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load users:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function renderUsers(users) {
|
||||
const tbody = document.getElementById("usersTableBody");
|
||||
if (users.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr><td colspan="8" class="text-center p-4">
|
||||
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
|
||||
<p class="mt-3 text-muted">No users found</p>
|
||||
<button class="btn btn-primary" onclick="showCreateUser()">
|
||||
<i class="bi bi-person-plus"></i> Create First User
|
||||
</button>
|
||||
</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = users
|
||||
.map(
|
||||
(u) => `
|
||||
<tr>
|
||||
<td>${u.id}</td>
|
||||
<td><strong>${escapeHtml(u.name)}</strong></td>
|
||||
<td>${escapeHtml(u.email)}</td>
|
||||
<td>@${escapeHtml(u.username)}</td>
|
||||
<td><span class="role-badge role-${u.role
|
||||
.toLowerCase()
|
||||
.replace(" ", "-")}">${u.role}</span></td>
|
||||
<td><span class="badge ${u.isactive ? "badge-success" : "badge-danger"}">
|
||||
${u.isactive ? "Active" : "Disabled"}</span></td>
|
||||
<td>${formatDate(u.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editUser(${
|
||||
u.id
|
||||
})" title="Edit User">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-warning" onclick="showChangePassword(${
|
||||
u.id
|
||||
}, '${escapeHtml(u.name)}')" title="Change Password">
|
||||
<i class="bi bi-key"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteUser(${
|
||||
u.id
|
||||
}, '${escapeHtml(u.name)}')" title="Delete User">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
function filterUsers() {
|
||||
const searchTerm = document.getElementById("searchInput").value.toLowerCase();
|
||||
const filtered = usersData.filter(
|
||||
(u) =>
|
||||
u.name.toLowerCase().includes(searchTerm) ||
|
||||
u.email.toLowerCase().includes(searchTerm) ||
|
||||
u.username.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
renderUsers(filtered);
|
||||
}
|
||||
|
||||
function showCreateUser() {
|
||||
document.getElementById("modalTitle").textContent = "Create New User";
|
||||
document.getElementById("userForm").reset();
|
||||
document.getElementById("userId").value = "";
|
||||
document.getElementById("userActive").checked = true;
|
||||
document.getElementById("userPasswordNeverExpires").checked = false;
|
||||
document.getElementById("userRole").value = "Cashier";
|
||||
updatePermissionsPreview();
|
||||
userModal.show();
|
||||
}
|
||||
|
||||
async function editUser(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${id}`, {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
const user = data.user;
|
||||
document.getElementById("modalTitle").textContent = "Edit User";
|
||||
document.getElementById("userId").value = user.id;
|
||||
document.getElementById("userName").value = user.name;
|
||||
document.getElementById("userUsername").value = user.username;
|
||||
document.getElementById("userEmail").value = user.email;
|
||||
document.getElementById("userPassword").value = "";
|
||||
document.getElementById("userPasswordConfirm").value = "";
|
||||
document.getElementById("userRole").value = user.role;
|
||||
document.getElementById("userActive").checked = user.isactive;
|
||||
document.getElementById("userPasswordNeverExpires").checked =
|
||||
user.passwordneverexpires || false;
|
||||
updatePermissionsPreview();
|
||||
userModal.show();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load user:", error);
|
||||
showError("Failed to load user details");
|
||||
}
|
||||
}
|
||||
|
||||
async function saveUser() {
|
||||
const id = document.getElementById("userId").value;
|
||||
const password = document.getElementById("userPassword").value;
|
||||
const passwordConfirm = document.getElementById("userPasswordConfirm").value;
|
||||
|
||||
// Password validation (only for new users or when changing password)
|
||||
if (!id || password) {
|
||||
if (!password) {
|
||||
showError("Password is required for new users");
|
||||
return;
|
||||
}
|
||||
if (password !== passwordConfirm) {
|
||||
showError("Passwords do not match");
|
||||
return;
|
||||
}
|
||||
if (password.length < 8) {
|
||||
showError("Password must be at least 8 characters long");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const formData = {
|
||||
name: document.getElementById("userName").value,
|
||||
username: document.getElementById("userUsername").value,
|
||||
email: document.getElementById("userEmail").value,
|
||||
role: document.getElementById("userRole").value,
|
||||
isactive: document.getElementById("userActive").checked,
|
||||
passwordneverexpires: document.getElementById("userPasswordNeverExpires")
|
||||
.checked,
|
||||
};
|
||||
|
||||
if (password) {
|
||||
formData.password = password;
|
||||
}
|
||||
|
||||
if (!formData.name || !formData.username || !formData.email) {
|
||||
showError("Please fill in all required fields");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = id ? `/api/admin/users/${id}` : "/api/admin/users";
|
||||
const method = id ? "PUT" : "POST";
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess(
|
||||
id ? "User updated successfully" : "User created successfully"
|
||||
);
|
||||
userModal.hide();
|
||||
loadUsers();
|
||||
} else {
|
||||
showError(data.message || "Failed to save user");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save user:", error);
|
||||
showError("Failed to save user");
|
||||
}
|
||||
}
|
||||
|
||||
function showChangePassword(id, name) {
|
||||
document.getElementById("passwordUserId").value = id;
|
||||
document.getElementById("passwordUserName").value = name;
|
||||
document.getElementById("passwordUserDisplay").textContent = name;
|
||||
document.getElementById("passwordForm").reset();
|
||||
passwordModal.show();
|
||||
}
|
||||
|
||||
async function changePassword() {
|
||||
const id = document.getElementById("passwordUserId").value;
|
||||
const newPassword = document.getElementById("newPassword").value;
|
||||
const confirmPassword = document.getElementById("confirmNewPassword").value;
|
||||
|
||||
if (!newPassword || !confirmPassword) {
|
||||
showError("Please enter and confirm the new password");
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
showError("Passwords do not match");
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword.length < 8) {
|
||||
showError("Password must be at least 8 characters long");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${id}/password`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ password: newPassword }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess("Password changed successfully");
|
||||
passwordModal.hide();
|
||||
} else {
|
||||
showError(data.message || "Failed to change password");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to change password:", error);
|
||||
showError("Failed to change password");
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteUser(id, name) {
|
||||
if (
|
||||
!confirm(
|
||||
`Are you sure you want to delete user "${name}"? This action cannot be undone.`
|
||||
)
|
||||
)
|
||||
return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess("User deleted successfully");
|
||||
loadUsers();
|
||||
} else {
|
||||
showError(data.message || "Failed to delete user");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete user:", error);
|
||||
showError("Failed to delete user");
|
||||
}
|
||||
}
|
||||
|
||||
function updatePermissionsPreview() {
|
||||
const role = document.getElementById("userRole").value;
|
||||
const permissions = rolePermissions[role] || [];
|
||||
const container = document.getElementById("permissionsPreview");
|
||||
|
||||
container.innerHTML = permissions
|
||||
.map(
|
||||
(perm) => `
|
||||
<div class="permission-item active">
|
||||
<i class="bi bi-check-circle-fill" style="color: #10b981; margin-right: 8px;"></i>
|
||||
<span>${perm}</span>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
if (response.ok) window.location.href = "/admin/login.html";
|
||||
} catch (error) {
|
||||
console.error("Logout failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
};
|
||||
return text.replace(/[&<>"']/g, (m) => map[m]);
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
return new Date(dateString).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
alert(message);
|
||||
}
|
||||
function showError(message) {
|
||||
alert("Error: " + message);
|
||||
}
|
||||
535
website/admin/media-library.html
Normal file
535
website/admin/media-library.html
Normal file
@@ -0,0 +1,535 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Media Library - Sky Art Shop</title>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="/admin/css/admin-style.css" />
|
||||
<style>
|
||||
.media-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
padding: 20px;
|
||||
}
|
||||
.media-item {
|
||||
position: relative;
|
||||
border: 2px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.media-item:hover {
|
||||
border-color: #7c3aed;
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(124, 58, 237, 0.3);
|
||||
}
|
||||
.media-item.selected {
|
||||
border-color: #7c3aed;
|
||||
border-width: 3px;
|
||||
}
|
||||
.media-item img {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
}
|
||||
.media-item-name {
|
||||
padding: 10px;
|
||||
background: #f8f9fa;
|
||||
font-size: 12px;
|
||||
word-break: break-all;
|
||||
}
|
||||
.media-item-actions {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
display: none;
|
||||
}
|
||||
.media-item:hover .media-item-actions {
|
||||
display: block;
|
||||
}
|
||||
.upload-zone {
|
||||
border: 3px dashed #dee2e6;
|
||||
border-radius: 10px;
|
||||
padding: 60px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
.upload-zone:hover,
|
||||
.upload-zone.dragover {
|
||||
border-color: #7c3aed;
|
||||
background: #f3f0ff;
|
||||
}
|
||||
.upload-zone i {
|
||||
font-size: 48px;
|
||||
color: #7c3aed;
|
||||
}
|
||||
.toolbar {
|
||||
background: #fff;
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.selected-count {
|
||||
background: #7c3aed;
|
||||
color: white;
|
||||
padding: 5px 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products.html"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog.html"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html" 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>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings.html"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users.html"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content">
|
||||
<!-- Top Bar -->
|
||||
<div class="top-bar">
|
||||
<div>
|
||||
<h3>Media Library</h3>
|
||||
<p class="mb-0 text-muted">Manage your images and media files</p>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn-logout" onclick="logout()">
|
||||
<i class="bi bi-box-arrow-right"></i> Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="toolbar">
|
||||
<div>
|
||||
<span
|
||||
class="selected-count"
|
||||
id="selectedCount"
|
||||
style="display: none"
|
||||
>0 selected</span
|
||||
>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary" id="uploadBtn">
|
||||
<i class="bi bi-cloud-upload"></i> Upload Files
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-success"
|
||||
id="selectBtn"
|
||||
style="display: none"
|
||||
>
|
||||
<i class="bi bi-check-lg"></i> Select
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" id="closeBtn">
|
||||
<i class="bi bi-x-lg"></i> Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid p-4">
|
||||
<!-- Upload Zone -->
|
||||
<div class="upload-zone mb-4" id="uploadZone" style="display: none">
|
||||
<i class="bi bi-cloud-arrow-up"></i>
|
||||
<h4 class="mt-3">Drop files here or click to browse</h4>
|
||||
<p class="text-muted">
|
||||
Supported: JPG, PNG, GIF, WebP (Max 5MB each)
|
||||
</p>
|
||||
<input
|
||||
type="file"
|
||||
id="fileInput"
|
||||
multiple
|
||||
accept="image/*"
|
||||
style="display: none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Upload Progress -->
|
||||
<div id="uploadProgress" style="display: none" class="mb-4">
|
||||
<div class="progress" style="height: 30px">
|
||||
<div
|
||||
class="progress-bar progress-bar-striped progress-bar-animated"
|
||||
role="progressbar"
|
||||
style="width: 0%"
|
||||
id="progressBar"
|
||||
>
|
||||
0%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search and Filter -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="searchInput"
|
||||
placeholder="Search files..."
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select class="form-select" id="filterType">
|
||||
<option value="all">All Types</option>
|
||||
<option value="image">Images</option>
|
||||
<option value="recent">Recently Uploaded</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button
|
||||
class="btn btn-outline-danger w-100"
|
||||
id="deleteSelectedBtn"
|
||||
style="display: none"
|
||||
>
|
||||
<i class="bi bi-trash"></i> Delete Selected
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Media Grid -->
|
||||
<div class="media-grid" id="mediaGrid">
|
||||
<!-- Media items will be loaded here -->
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div id="emptyState" style="display: none" class="text-center py-5">
|
||||
<i class="bi bi-images" style="font-size: 64px; color: #dee2e6"></i>
|
||||
<h4 class="mt-3 text-muted">No files yet</h4>
|
||||
<p class="text-muted">Upload your first image to get started</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Main Content -->
|
||||
|
||||
<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>
|
||||
let selectedFiles = [];
|
||||
let allFiles = [];
|
||||
let allowMultiple = false;
|
||||
|
||||
// Initialize
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
init();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function init() {
|
||||
// Get parameters from URL
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
allowMultiple = urlParams.get("multiple") === "true";
|
||||
const callback = urlParams.get("callback");
|
||||
|
||||
// Setup event listeners
|
||||
document
|
||||
.getElementById("uploadBtn")
|
||||
.addEventListener("click", showUploadZone);
|
||||
document
|
||||
.getElementById("uploadZone")
|
||||
.addEventListener("click", () =>
|
||||
document.getElementById("fileInput").click()
|
||||
);
|
||||
document
|
||||
.getElementById("fileInput")
|
||||
.addEventListener("change", handleFileSelect);
|
||||
document
|
||||
.getElementById("selectBtn")
|
||||
.addEventListener("click", handleSelect);
|
||||
document
|
||||
.getElementById("closeBtn")
|
||||
.addEventListener("click", () => window.close());
|
||||
document
|
||||
.getElementById("deleteSelectedBtn")
|
||||
.addEventListener("click", handleDeleteSelected);
|
||||
document
|
||||
.getElementById("searchInput")
|
||||
.addEventListener("input", handleSearch);
|
||||
document
|
||||
.getElementById("filterType")
|
||||
.addEventListener("change", handleFilter);
|
||||
|
||||
// Drag and drop
|
||||
const uploadZone = document.getElementById("uploadZone");
|
||||
uploadZone.addEventListener("dragover", (e) => {
|
||||
e.preventDefault();
|
||||
uploadZone.classList.add("dragover");
|
||||
});
|
||||
uploadZone.addEventListener("dragleave", () => {
|
||||
uploadZone.classList.remove("dragover");
|
||||
});
|
||||
uploadZone.addEventListener("drop", handleDrop);
|
||||
|
||||
loadFiles();
|
||||
}
|
||||
|
||||
function showUploadZone() {
|
||||
document.getElementById("uploadZone").style.display = "block";
|
||||
}
|
||||
|
||||
async function loadFiles() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/uploads", {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
allFiles = data.files;
|
||||
renderFiles(allFiles);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load files:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function renderFiles(files) {
|
||||
const grid = document.getElementById("mediaGrid");
|
||||
const emptyState = document.getElementById("emptyState");
|
||||
|
||||
if (files.length === 0) {
|
||||
grid.style.display = "none";
|
||||
emptyState.style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
grid.style.display = "grid";
|
||||
emptyState.style.display = "none";
|
||||
|
||||
grid.innerHTML = files
|
||||
.map(
|
||||
(file) => `
|
||||
<div class="media-item" data-file="${file.filename}" onclick="toggleSelect('${file.filename}')">
|
||||
<img src="/uploads/${file.filename}" alt="${file.filename}">
|
||||
<div class="media-item-name">${file.filename}</div>
|
||||
<div class="media-item-actions">
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteFile(event, '${file.filename}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
function toggleSelect(filename) {
|
||||
const item = document.querySelector(`[data-file="${filename}"]`);
|
||||
|
||||
if (!allowMultiple) {
|
||||
// Clear other selections
|
||||
document.querySelectorAll(".media-item.selected").forEach((el) => {
|
||||
if (el.dataset.file !== filename) {
|
||||
el.classList.remove("selected");
|
||||
}
|
||||
});
|
||||
selectedFiles = [];
|
||||
}
|
||||
|
||||
const index = selectedFiles.indexOf(filename);
|
||||
if (index > -1) {
|
||||
selectedFiles.splice(index, 1);
|
||||
item.classList.remove("selected");
|
||||
} else {
|
||||
selectedFiles.push(filename);
|
||||
item.classList.add("selected");
|
||||
}
|
||||
|
||||
updateSelection();
|
||||
}
|
||||
|
||||
function updateSelection() {
|
||||
const countEl = document.getElementById("selectedCount");
|
||||
const selectBtn = document.getElementById("selectBtn");
|
||||
const deleteBtn = document.getElementById("deleteSelectedBtn");
|
||||
|
||||
if (selectedFiles.length > 0) {
|
||||
countEl.textContent = `${selectedFiles.length} selected`;
|
||||
countEl.style.display = "block";
|
||||
selectBtn.style.display = "block";
|
||||
deleteBtn.style.display = "block";
|
||||
} else {
|
||||
countEl.style.display = "none";
|
||||
selectBtn.style.display = "none";
|
||||
deleteBtn.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
function handleSelect() {
|
||||
if (window.opener && window.opener.receiveMediaFiles) {
|
||||
const files = selectedFiles.map((f) => `/uploads/${f}`);
|
||||
window.opener.receiveMediaFiles(allowMultiple ? files : files[0]);
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFileSelect(e) {
|
||||
const files = Array.from(e.target.files);
|
||||
await uploadFiles(files);
|
||||
}
|
||||
|
||||
async function handleDrop(e) {
|
||||
e.preventDefault();
|
||||
e.currentTarget.classList.remove("dragover");
|
||||
|
||||
const files = Array.from(e.dataTransfer.files);
|
||||
await uploadFiles(files);
|
||||
}
|
||||
|
||||
async function uploadFiles(files) {
|
||||
const formData = new FormData();
|
||||
files.forEach((file) => formData.append("files", file));
|
||||
|
||||
const progressBar = document.getElementById("progressBar");
|
||||
const progressContainer = document.getElementById("uploadProgress");
|
||||
progressContainer.style.display = "block";
|
||||
|
||||
try {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.upload.addEventListener("progress", (e) => {
|
||||
if (e.lengthComputable) {
|
||||
const percentComplete = (e.loaded / e.total) * 100;
|
||||
progressBar.style.width = percentComplete + "%";
|
||||
progressBar.textContent = Math.round(percentComplete) + "%";
|
||||
}
|
||||
});
|
||||
|
||||
xhr.addEventListener("load", function () {
|
||||
if (xhr.status === 200) {
|
||||
const data = JSON.parse(xhr.responseText);
|
||||
if (data.success) {
|
||||
setTimeout(() => {
|
||||
progressContainer.style.display = "none";
|
||||
document.getElementById("uploadZone").style.display = "none";
|
||||
loadFiles();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
xhr.open("POST", "/api/admin/upload");
|
||||
xhr.withCredentials = true;
|
||||
xhr.send(formData);
|
||||
} catch (error) {
|
||||
console.error("Upload failed:", error);
|
||||
alert("Upload failed: " + error.message);
|
||||
progressContainer.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteFile(event, filename) {
|
||||
event.stopPropagation();
|
||||
|
||||
if (!confirm("Delete this file?")) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/uploads/${filename}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
loadFiles();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete file:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!confirm(`Delete ${selectedFiles.length} files?`)) return;
|
||||
|
||||
for (const filename of selectedFiles) {
|
||||
await deleteFile(new Event("click"), filename);
|
||||
}
|
||||
|
||||
selectedFiles = [];
|
||||
updateSelection();
|
||||
}
|
||||
|
||||
function handleSearch(e) {
|
||||
const query = e.target.value.toLowerCase();
|
||||
const filtered = allFiles.filter((f) =>
|
||||
f.filename.toLowerCase().includes(query)
|
||||
);
|
||||
renderFiles(filtered);
|
||||
}
|
||||
|
||||
function handleFilter(e) {
|
||||
const filter = e.target.value;
|
||||
let filtered = allFiles;
|
||||
|
||||
if (filter === "recent") {
|
||||
filtered = allFiles
|
||||
.slice()
|
||||
.sort((a, b) => new Date(b.uploadDate) - new Date(a.uploadDate))
|
||||
.slice(0, 20);
|
||||
}
|
||||
|
||||
renderFiles(filtered);
|
||||
}
|
||||
</script>
|
||||
<script src="/admin/js/auth.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
420
website/admin/menu.html
Normal file
420
website/admin/menu.html
Normal file
@@ -0,0 +1,420 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Menu Management - Sky Art Shop</title>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="/admin/css/admin-style.css" />
|
||||
<style>
|
||||
.menu-item {
|
||||
background: white;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: all 0.3s ease;
|
||||
cursor: move;
|
||||
}
|
||||
.menu-item:hover {
|
||||
border-color: #667eea;
|
||||
transform: translateX(5px);
|
||||
}
|
||||
.menu-item-content {
|
||||
flex: 1;
|
||||
}
|
||||
.menu-item-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.drag-handle {
|
||||
cursor: grab;
|
||||
color: #6c757d;
|
||||
margin-right: 15px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products.html"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog.html"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html" 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>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users.html"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="top-bar">
|
||||
<div>
|
||||
<h3>Menu Management</h3>
|
||||
<p class="mb-0 text-muted">Organize your website navigation</p>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn-logout" onclick="logout()">
|
||||
<i class="bi bi-box-arrow-right"></i> Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions-bar">
|
||||
<button class="btn btn-primary" onclick="showAddMenuItem()">
|
||||
<i class="bi bi-plus-circle"></i> Add Menu Item
|
||||
</button>
|
||||
<button class="btn btn-success" onclick="saveMenuOrder()">
|
||||
<i class="bi bi-save"></i> Save Order
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="p-4">
|
||||
<h5 class="mb-3">Main Navigation Menu</h5>
|
||||
<small class="text-muted">Drag and drop to reorder menu items</small>
|
||||
<div id="menuItems" class="mt-3">
|
||||
<div class="text-center p-4">
|
||||
<div class="loading-spinner"></div>
|
||||
Loading menu items...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Menu Item Modal -->
|
||||
<div class="modal fade" id="menuModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalTitle">Add Menu Item</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="menuForm">
|
||||
<input type="hidden" id="menuItemId" />
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="menuLabel" class="form-label">Label *</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="menuLabel"
|
||||
required
|
||||
placeholder="Home, Shop, About..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="menuUrl" class="form-label">URL *</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="menuUrl"
|
||||
required
|
||||
placeholder="/shop, /about, /contact"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="menuIcon" class="form-label">Icon (optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="menuIcon"
|
||||
placeholder="bi-house, bi-shop, etc."
|
||||
/>
|
||||
<small class="text-muted">Bootstrap Icon name</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="menuVisible"
|
||||
checked
|
||||
/>
|
||||
<label class="form-check-label" for="menuVisible">
|
||||
Visible in menu
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
onclick="saveMenuItem()"
|
||||
>
|
||||
<i class="bi bi-save"></i> Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
let menuItemsData = [];
|
||||
let menuModal;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
menuModal = new bootstrap.Modal(document.getElementById("menuModal"));
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadMenuItems();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function loadMenuItems() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/menu", {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
menuItemsData = data.items || [];
|
||||
renderMenuItems();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load menu items:", error);
|
||||
menuItemsData = [];
|
||||
renderMenuItems();
|
||||
}
|
||||
}
|
||||
|
||||
function renderMenuItems() {
|
||||
const container = document.getElementById("menuItems");
|
||||
if (menuItemsData.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="text-center p-4">
|
||||
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
|
||||
<p class="mt-3 text-muted">No menu items yet</p>
|
||||
<button class="btn btn-primary" onclick="showAddMenuItem()">
|
||||
<i class="bi bi-plus-circle"></i> Add Your First Menu Item
|
||||
</button>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = menuItemsData
|
||||
.map(
|
||||
(item, index) => `
|
||||
<div class="menu-item" draggable="true" data-index="${index}">
|
||||
<div class="d-flex align-items-center flex-grow-1">
|
||||
<i class="bi bi-grip-vertical drag-handle"></i>
|
||||
<div class="menu-item-content">
|
||||
<strong>${item.label}</strong>
|
||||
<div class="text-muted small">${item.url}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-item-actions">
|
||||
<span class="badge ${
|
||||
item.visible ? "badge-success" : "badge-danger"
|
||||
}">
|
||||
${item.visible ? "Visible" : "Hidden"}
|
||||
</span>
|
||||
<button class="btn btn-sm btn-info" onclick="editMenuItem(${index})">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteMenuItem(${index})">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
|
||||
// Add drag and drop functionality
|
||||
addDragAndDrop();
|
||||
}
|
||||
|
||||
function addDragAndDrop() {
|
||||
const items = document.querySelectorAll(".menu-item");
|
||||
items.forEach((item) => {
|
||||
item.addEventListener("dragstart", handleDragStart);
|
||||
item.addEventListener("dragover", handleDragOver);
|
||||
item.addEventListener("drop", handleDrop);
|
||||
item.addEventListener("dragend", handleDragEnd);
|
||||
});
|
||||
}
|
||||
|
||||
let draggedItem = null;
|
||||
|
||||
function handleDragStart(e) {
|
||||
draggedItem = this;
|
||||
e.dataTransfer.effectAllowed = "move";
|
||||
}
|
||||
|
||||
function handleDragOver(e) {
|
||||
if (e.preventDefault) e.preventDefault();
|
||||
e.dataTransfer.dropEffect = "move";
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleDrop(e) {
|
||||
if (e.stopPropagation) e.stopPropagation();
|
||||
if (draggedItem !== this) {
|
||||
const fromIndex = parseInt(draggedItem.dataset.index);
|
||||
const toIndex = parseInt(this.dataset.index);
|
||||
const item = menuItemsData.splice(fromIndex, 1)[0];
|
||||
menuItemsData.splice(toIndex, 0, item);
|
||||
renderMenuItems();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleDragEnd(e) {
|
||||
draggedItem = null;
|
||||
}
|
||||
|
||||
function showAddMenuItem() {
|
||||
document.getElementById("modalTitle").textContent = "Add Menu Item";
|
||||
document.getElementById("menuForm").reset();
|
||||
document.getElementById("menuItemId").value = "";
|
||||
document.getElementById("menuVisible").checked = true;
|
||||
menuModal.show();
|
||||
}
|
||||
|
||||
function editMenuItem(index) {
|
||||
const item = menuItemsData[index];
|
||||
document.getElementById("modalTitle").textContent = "Edit Menu Item";
|
||||
document.getElementById("menuItemId").value = index;
|
||||
document.getElementById("menuLabel").value = item.label;
|
||||
document.getElementById("menuUrl").value = item.url;
|
||||
document.getElementById("menuIcon").value = item.icon || "";
|
||||
document.getElementById("menuVisible").checked = item.visible !== false;
|
||||
menuModal.show();
|
||||
}
|
||||
|
||||
function saveMenuItem() {
|
||||
const index = document.getElementById("menuItemId").value;
|
||||
const item = {
|
||||
label: document.getElementById("menuLabel").value,
|
||||
url: document.getElementById("menuUrl").value,
|
||||
icon: document.getElementById("menuIcon").value,
|
||||
visible: document.getElementById("menuVisible").checked,
|
||||
};
|
||||
|
||||
if (!item.label || !item.url) {
|
||||
alert("Label and URL are required");
|
||||
return;
|
||||
}
|
||||
|
||||
if (index === "") {
|
||||
menuItemsData.push(item);
|
||||
} else {
|
||||
menuItemsData[parseInt(index)] = item;
|
||||
}
|
||||
|
||||
menuModal.hide();
|
||||
renderMenuItems();
|
||||
saveMenuOrder();
|
||||
}
|
||||
|
||||
function deleteMenuItem(index) {
|
||||
if (confirm("Are you sure you want to delete this menu item?")) {
|
||||
menuItemsData.splice(index, 1);
|
||||
renderMenuItems();
|
||||
saveMenuOrder();
|
||||
}
|
||||
}
|
||||
|
||||
async function saveMenuOrder() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/menu", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ items: menuItemsData }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
alert("Menu saved successfully!");
|
||||
} else {
|
||||
alert("Failed to save menu: " + (data.message || ""));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save menu:", error);
|
||||
alert("Failed to save menu");
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
if (response.ok) window.location.href = "/admin/login.html";
|
||||
} catch (error) {
|
||||
console.error("Logout failed:", error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="/admin/js/auth.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
222
website/admin/pages.html
Normal file
222
website/admin/pages.html
Normal file
@@ -0,0 +1,222 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Custom Pages - Sky Art Shop</title>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="/admin/css/admin-style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products.html"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog.html"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html" class="active"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings.html"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users.html"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="top-bar">
|
||||
<div>
|
||||
<h3>Custom Pages Management</h3>
|
||||
<p class="mb-0 text-muted">Create and manage custom pages</p>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn-logout" onclick="logout()">
|
||||
<i class="bi bi-box-arrow-right"></i> Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions-bar">
|
||||
<button class="btn btn-primary" onclick="showCreatePage()">
|
||||
<i class="bi bi-plus-circle"></i> Create New Page
|
||||
</button>
|
||||
<div class="search-box">
|
||||
<i class="bi bi-search"></i>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search pages..."
|
||||
id="searchInput"
|
||||
oninput="filterPages()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Page Title</th>
|
||||
<th>Slug</th>
|
||||
<th>Status</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="pagesTableBody">
|
||||
<tr>
|
||||
<td colspan="6" class="text-center">
|
||||
<div class="loading-spinner"></div>
|
||||
Loading pages...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="pageModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalTitle">Create Custom Page</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="pageForm">
|
||||
<input type="hidden" id="pageId" />
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="pageTitle" class="form-label">Page Title *</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="pageTitle"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="pageSlug" class="form-label">Slug *</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="pageSlug"
|
||||
required
|
||||
/>
|
||||
<small class="text-muted"
|
||||
>URL path (e.g., about-us, contact)</small
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="pageContent" class="form-label"
|
||||
>Page Content *</label
|
||||
>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="pageContent"
|
||||
rows="15"
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="pageMetaTitle" class="form-label"
|
||||
>Meta Title (SEO)</label
|
||||
>
|
||||
<input type="text" class="form-control" id="pageMetaTitle" />
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="pageMetaDescription" class="form-label"
|
||||
>Meta Description (SEO)</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="pageMetaDescription"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="pagePublished"
|
||||
checked
|
||||
/>
|
||||
<label class="form-check-label" for="pagePublished">
|
||||
Published (visible on website)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="savePage()">
|
||||
<i class="bi bi-save"></i> Save Page
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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/pages.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
225
website/admin/portfolio.html
Normal file
225
website/admin/portfolio.html
Normal file
@@ -0,0 +1,225 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Portfolio Management - Sky Art Shop</title>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="/admin/css/admin-style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products.html"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html" 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>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings.html"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users.html"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="top-bar">
|
||||
<div>
|
||||
<h3>Portfolio Management</h3>
|
||||
<p class="mb-0 text-muted">Showcase your creative work</p>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn-logout" onclick="logout()">
|
||||
<i class="bi bi-box-arrow-right"></i> Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions-bar">
|
||||
<button class="btn btn-primary" onclick="showCreateProject()">
|
||||
<i class="bi bi-plus-circle"></i> Add Portfolio Project
|
||||
</button>
|
||||
<div class="search-box">
|
||||
<i class="bi bi-search"></i>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search projects..."
|
||||
id="searchInput"
|
||||
oninput="filterProjects()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Project Title</th>
|
||||
<th>Description</th>
|
||||
<th>Category</th>
|
||||
<th>Status</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="projectsTableBody">
|
||||
<tr>
|
||||
<td colspan="7" class="text-center">
|
||||
<div class="loading-spinner"></div>
|
||||
Loading projects...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="projectModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalTitle">Add Portfolio Project</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="projectForm">
|
||||
<input type="hidden" id="projectId" />
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="projectTitle" class="form-label"
|
||||
>Project Title *</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="projectTitle"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="projectDescription" class="form-label"
|
||||
>Description *</label
|
||||
>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="projectDescription"
|
||||
rows="4"
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="projectCategory" class="form-label"
|
||||
>Category/Tag</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="projectCategory"
|
||||
placeholder="e.g., Digital Art, Photography"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="projectImages" class="form-label"
|
||||
>Project Images/Gallery</label
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
class="form-control"
|
||||
id="projectImages"
|
||||
multiple
|
||||
accept="image/*"
|
||||
/>
|
||||
<small class="text-muted"
|
||||
>Upload multiple images for gallery</small
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="projectActive"
|
||||
checked
|
||||
/>
|
||||
<label class="form-check-label" for="projectActive">
|
||||
Active (Show on website)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
onclick="saveProject()"
|
||||
>
|
||||
<i class="bi bi-save"></i> Save Project
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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/portfolio.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
257
website/admin/products.html
Normal file
257
website/admin/products.html
Normal file
@@ -0,0 +1,257 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Products Management - Sky Art Shop</title>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="/admin/css/admin-style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar" id="sidebar">
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products.html" class="active"
|
||||
><i class="bi bi-box"></i> Products</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog.html"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings.html"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users.html"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content">
|
||||
<!-- Top Bar -->
|
||||
<div class="top-bar">
|
||||
<div>
|
||||
<h3>Products Management</h3>
|
||||
<p class="mb-0 text-muted">Manage your product catalog</p>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn-logout" onclick="logout()">
|
||||
<i class="bi bi-box-arrow-right"></i> Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions Bar -->
|
||||
<div class="actions-bar">
|
||||
<button class="btn btn-primary" onclick="showCreateProduct()">
|
||||
<i class="bi bi-plus-circle"></i> Add New Product
|
||||
</button>
|
||||
<div class="search-box">
|
||||
<i class="bi bi-search"></i>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search products..."
|
||||
id="searchInput"
|
||||
oninput="filterProducts()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Products Table -->
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Product Name</th>
|
||||
<th>Price</th>
|
||||
<th>Stock</th>
|
||||
<th>Status</th>
|
||||
<th>Best Seller</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="productsTableBody">
|
||||
<tr>
|
||||
<td colspan="8" class="text-center">
|
||||
<div class="loading-spinner"></div>
|
||||
Loading products...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create/Edit Product Modal -->
|
||||
<div class="modal fade" id="productModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalTitle">Add New Product</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="productForm">
|
||||
<input type="hidden" id="productId" />
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="productName" class="form-label"
|
||||
>Product Name *</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="productName"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="productDescription" class="form-label"
|
||||
>Description</label
|
||||
>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="productDescription"
|
||||
rows="4"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="productPrice" class="form-label">Price *</label>
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
class="form-control"
|
||||
id="productPrice"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="productStock" class="form-label"
|
||||
>Stock Quantity</label
|
||||
>
|
||||
<input type="number" class="form-control" id="productStock" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="productCategory" class="form-label">Category</label>
|
||||
<input type="text" class="form-control" id="productCategory" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="productImages" class="form-label"
|
||||
>Product Images</label
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
class="form-control"
|
||||
id="productImages"
|
||||
multiple
|
||||
accept="image/*"
|
||||
/>
|
||||
<small class="text-muted">You can upload multiple images</small>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="productActive"
|
||||
checked
|
||||
/>
|
||||
<label class="form-check-label" for="productActive">
|
||||
Active (Visible on website)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="productBestSeller"
|
||||
/>
|
||||
<label class="form-check-label" for="productBestSeller">
|
||||
Mark as Best Seller
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
onclick="saveProduct()"
|
||||
>
|
||||
<i class="bi bi-save"></i> Save & Publish
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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/products.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
538
website/admin/settings.html
Normal file
538
website/admin/settings.html
Normal file
@@ -0,0 +1,538 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Settings - Sky Art Shop</title>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="/admin/css/admin-style.css" />
|
||||
<style>
|
||||
.settings-section {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.settings-section h4 {
|
||||
color: #2c3e50;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 2px solid #e9ecef;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.logo-preview {
|
||||
width: 200px;
|
||||
height: 80px;
|
||||
border: 2px dashed #ccc;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
overflow: hidden;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.logo-preview img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.favicon-preview {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border: 2px dashed #ccc;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
overflow: hidden;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.favicon-preview img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.theme-selector {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.theme-option {
|
||||
padding: 20px;
|
||||
border: 3px solid #e9ecef;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.theme-option:hover {
|
||||
border-color: #667eea;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.theme-option.active {
|
||||
border-color: #667eea;
|
||||
background: linear-gradient(135deg, #667eea11 0%, #764ba222 100%);
|
||||
}
|
||||
|
||||
.theme-option i {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.color-picker-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.color-preview {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #e9ecef;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard.html"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage.html"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products.html"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio.html"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog.html"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages.html"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library.html"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu.html"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings.html" 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>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="top-bar">
|
||||
<div>
|
||||
<h3>Settings</h3>
|
||||
<p class="mb-0 text-muted">Configure your website</p>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn-logout" onclick="logout()">
|
||||
<i class="bi bi-box-arrow-right"></i> Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- General Settings -->
|
||||
<div class="settings-section">
|
||||
<h4><i class="bi bi-gear-fill"></i> General Settings</h4>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="siteName" class="form-label">Website Name *</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="siteName"
|
||||
placeholder="Sky Art Shop"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="siteTagline" class="form-label">Tagline</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="siteTagline"
|
||||
placeholder="Your Creative Destination"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="siteEmail" class="form-label">Contact Email</label>
|
||||
<input
|
||||
type="email"
|
||||
class="form-control"
|
||||
id="siteEmail"
|
||||
placeholder="info@skyartshop.com"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="sitePhone" class="form-label">Phone Number</label>
|
||||
<input
|
||||
type="tel"
|
||||
class="form-control"
|
||||
id="sitePhone"
|
||||
placeholder="+1 234 567 8900"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="siteLogo" class="form-label">Logo</label>
|
||||
<input
|
||||
type="file"
|
||||
class="form-control"
|
||||
id="siteLogo"
|
||||
accept="image/*"
|
||||
onchange="previewLogo()"
|
||||
/>
|
||||
<div class="logo-preview" id="logoPreview">
|
||||
<span class="text-muted">No logo uploaded</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="siteFavicon" class="form-label">Favicon</label>
|
||||
<input
|
||||
type="file"
|
||||
class="form-control"
|
||||
id="siteFavicon"
|
||||
accept="image/*"
|
||||
onchange="previewFavicon()"
|
||||
/>
|
||||
<div class="favicon-preview" id="faviconPreview">
|
||||
<i class="bi bi-image text-muted"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="timezone" class="form-label">Timezone</label>
|
||||
<select class="form-control" id="timezone">
|
||||
<option value="UTC">UTC</option>
|
||||
<option value="America/New_York">Eastern Time (US & Canada)</option>
|
||||
<option value="America/Chicago">Central Time (US & Canada)</option>
|
||||
<option value="America/Denver">Mountain Time (US & Canada)</option>
|
||||
<option value="America/Los_Angeles">
|
||||
Pacific Time (US & Canada)
|
||||
</option>
|
||||
<option value="Europe/London">London</option>
|
||||
<option value="Europe/Paris">Paris</option>
|
||||
<option value="Asia/Tokyo">Tokyo</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Homepage Settings -->
|
||||
<div class="settings-section">
|
||||
<h4><i class="bi bi-house-fill"></i> Homepage Settings</h4>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Homepage Layout</label>
|
||||
<div class="theme-selector">
|
||||
<div class="theme-option active" onclick="selectLayout('modern')">
|
||||
<i class="bi bi-grid-3x3-gap"></i>
|
||||
<div><strong>Modern</strong></div>
|
||||
<small>Grid-based layout</small>
|
||||
</div>
|
||||
<div class="theme-option" onclick="selectLayout('classic')">
|
||||
<i class="bi bi-list-ul"></i>
|
||||
<div><strong>Classic</strong></div>
|
||||
<small>Traditional list</small>
|
||||
</div>
|
||||
<div class="theme-option" onclick="selectLayout('minimal')">
|
||||
<i class="bi bi-square"></i>
|
||||
<div><strong>Minimal</strong></div>
|
||||
<small>Clean & simple</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Featured Content</label>
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="showHero"
|
||||
checked
|
||||
/>
|
||||
<label class="form-check-label" for="showHero"
|
||||
>Show Hero Section</label
|
||||
>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="showPromotions"
|
||||
checked
|
||||
/>
|
||||
<label class="form-check-label" for="showPromotions"
|
||||
>Show Promotions</label
|
||||
>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="showPortfolio"
|
||||
checked
|
||||
/>
|
||||
<label class="form-check-label" for="showPortfolio"
|
||||
>Show Portfolio Showcase</label
|
||||
>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="showBlog"
|
||||
checked
|
||||
/>
|
||||
<label class="form-check-label" for="showBlog"
|
||||
>Show Recent Blog Posts</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product Settings -->
|
||||
<div class="settings-section">
|
||||
<h4><i class="bi bi-box-fill"></i> Product Settings</h4>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Default Product Status</label>
|
||||
<select class="form-control" id="defaultProductStatus">
|
||||
<option value="active">Active (Published)</option>
|
||||
<option value="draft">Draft (Hidden)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="productsPerPage" class="form-label"
|
||||
>Products Per Page</label
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
id="productsPerPage"
|
||||
value="12"
|
||||
min="6"
|
||||
max="48"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Best Seller Logic</label>
|
||||
<select class="form-control" id="bestSellerLogic">
|
||||
<option value="manual">Manual Selection</option>
|
||||
<option value="auto-sales">Automatic (Most Sales)</option>
|
||||
<option value="auto-views">Automatic (Most Views)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enableInventory"
|
||||
checked
|
||||
/>
|
||||
<label class="form-check-label" for="enableInventory"
|
||||
>Enable Inventory Management</label
|
||||
>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="showOutOfStock"
|
||||
checked
|
||||
/>
|
||||
<label class="form-check-label" for="showOutOfStock"
|
||||
>Show Out of Stock Products</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Security Settings -->
|
||||
<div class="settings-section">
|
||||
<h4><i class="bi bi-shield-fill"></i> Security Settings</h4>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="passwordExpiration" class="form-label"
|
||||
>Password Expiration (days)</label
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
id="passwordExpiration"
|
||||
value="90"
|
||||
min="0"
|
||||
/>
|
||||
<small class="text-muted">Set to 0 for never expires</small>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="sessionTimeout" class="form-label"
|
||||
>Session Timeout (minutes)</label
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
id="sessionTimeout"
|
||||
value="60"
|
||||
min="5"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="loginAttempts" class="form-label"
|
||||
>Max Login Attempts</label
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
id="loginAttempts"
|
||||
value="5"
|
||||
min="3"
|
||||
max="10"
|
||||
/>
|
||||
<small class="text-muted"
|
||||
>Number of failed attempts before account lockout</small
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="requireStrongPassword"
|
||||
checked
|
||||
/>
|
||||
<label class="form-check-label" for="requireStrongPassword">
|
||||
Require Strong Passwords (8+ chars, uppercase, lowercase, number)
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enableTwoFactor"
|
||||
/>
|
||||
<label class="form-check-label" for="enableTwoFactor"
|
||||
>Enable Two-Factor Authentication</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Appearance Settings -->
|
||||
<div class="settings-section">
|
||||
<h4><i class="bi bi-palette-fill"></i> Appearance Settings</h4>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Admin Panel Theme</label>
|
||||
<div class="theme-selector">
|
||||
<div class="theme-option active" onclick="selectTheme('light')">
|
||||
<i class="bi bi-sun-fill text-warning"></i>
|
||||
<div><strong>Light</strong></div>
|
||||
</div>
|
||||
<div class="theme-option" onclick="selectTheme('dark')">
|
||||
<i class="bi bi-moon-fill text-primary"></i>
|
||||
<div><strong>Dark</strong></div>
|
||||
</div>
|
||||
<div class="theme-option" onclick="selectTheme('auto')">
|
||||
<i class="bi bi-circle-half text-info"></i>
|
||||
<div><strong>Auto</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="accentColor" class="form-label">Accent Color</label>
|
||||
<div class="color-picker-wrapper">
|
||||
<input
|
||||
type="color"
|
||||
class="form-control"
|
||||
id="accentColor"
|
||||
value="#667eea"
|
||||
style="width: 80px"
|
||||
onchange="updateColorPreview()"
|
||||
/>
|
||||
<div
|
||||
class="color-preview"
|
||||
id="colorPreview"
|
||||
style="background-color: #667eea"
|
||||
></div>
|
||||
<span id="colorValue">#667eea</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save Button -->
|
||||
<div class="text-end">
|
||||
<button class="btn btn-lg btn-primary" onclick="saveSettings()">
|
||||
<i class="bi bi-save"></i> Save All Settings
|
||||
</button>
|
||||
</div>
|
||||
</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/settings.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,47 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>About - Sky Art Shop</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="/assets/css/main.css" />
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
</head>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Modern Navigation -->
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home.html" class="brand-link">
|
||||
<img src="/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg" alt="Sky Art Shop Logo" class="brand-logo" />
|
||||
<img
|
||||
src="/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg"
|
||||
alt="Sky Art Shop Logo"
|
||||
class="brand-logo"
|
||||
/>
|
||||
<span class="brand-name">Sky Art Shop</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="navbar-menu">
|
||||
<ul class="nav-menu-list">
|
||||
<li class="nav-item"><a href="/home.html" class="nav-link">Home</a></li>
|
||||
<li class="nav-item"><a href="/shop.html" class="nav-link">Shop</a></li>
|
||||
<li class="nav-item"><a href="/portfolio.html" class="nav-link">Portfolio</a></li>
|
||||
<li class="nav-item"><a href="/about.html" class="nav-link">About</a></li>
|
||||
<li class="nav-item"><a href="/blog.html" class="nav-link active">Blog</a></li>
|
||||
<li class="nav-item"><a href="/contact.html" class="nav-link">Contact</a></li>
|
||||
<li class="nav-item">
|
||||
<a href="/home.html" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop.html" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio.html" class="nav-link">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about.html" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog.html" class="nav-link active">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact.html" class="nav-link">Contact</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="navbar-actions">
|
||||
<div class="action-item wishlist-dropdown-wrapper">
|
||||
<button class="action-btn" id="wishlistToggle" aria-label="Wishlist">
|
||||
<button
|
||||
class="action-btn"
|
||||
id="wishlistToggle"
|
||||
aria-label="Wishlist"
|
||||
>
|
||||
<i class="bi bi-heart"></i>
|
||||
<span class="action-badge" id="wishlistCount">0</span>
|
||||
</button>
|
||||
<div class="action-dropdown wishlist-dropdown" id="wishlistPanel">
|
||||
<div class="dropdown-head">
|
||||
<h3>My Wishlist</h3>
|
||||
<button class="dropdown-close" id="wishlistClose"><i class="bi bi-x-lg"></i></button>
|
||||
<button class="dropdown-close" id="wishlistClose">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-body" id="wishlistContent">
|
||||
<p class="empty-state">Your wishlist is empty</p>
|
||||
@@ -51,16 +79,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="action-item cart-dropdown-wrapper">
|
||||
<button class="action-btn" id="cartToggle" aria-label="Shopping Cart">
|
||||
<button
|
||||
class="action-btn"
|
||||
id="cartToggle"
|
||||
aria-label="Shopping Cart"
|
||||
>
|
||||
<i class="bi bi-cart3"></i>
|
||||
<span class="action-badge" id="cartCount">0</span>
|
||||
</button>
|
||||
<div class="action-dropdown cart-dropdown" id="cartPanel">
|
||||
<div class="dropdown-head">
|
||||
<h3>Shopping Cart</h3>
|
||||
<button class="dropdown-close" id="cartClose"><i class="bi bi-x-lg"></i></button>
|
||||
<button class="dropdown-close" id="cartClose">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-body" id="cartContent">
|
||||
<p class="empty-state">Your cart is empty</p>
|
||||
@@ -70,12 +104,14 @@
|
||||
<span class="summary-label">Subtotal:</span>
|
||||
<span class="summary-value" id="cartSubtotal">$0.00</span>
|
||||
</div>
|
||||
<a href="/checkout.html" class="btn-primary-full">Proceed to Checkout</a>
|
||||
<a href="/checkout.html" class="btn-primary-full"
|
||||
>Proceed to Checkout</a
|
||||
>
|
||||
<a href="/shop.html" class="btn-text">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<button class="mobile-toggle" id="mobileMenuToggle" aria-label="Menu">
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
@@ -83,11 +119,13 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mobile-menu" id="mobileMenu">
|
||||
<div class="mobile-menu-header">
|
||||
<span class="mobile-brand">Sky Art Shop</span>
|
||||
<button class="mobile-close" id="mobileMenuClose"><i class="bi bi-x-lg"></i></button>
|
||||
<button class="mobile-close" id="mobileMenuClose">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<ul class="mobile-menu-list">
|
||||
<li><a href="/home.html" class="mobile-link">Home</a></li>
|
||||
@@ -99,157 +137,264 @@
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home.html" class="brand-link">
|
||||
<img src="/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg" alt="Sky Art Shop Logo" class="brand-logo" />
|
||||
<span class="brand-name">Sky Art Shop</span>
|
||||
</a>
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home.html" class="brand-link">
|
||||
<img
|
||||
src="/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg"
|
||||
alt="Sky Art Shop Logo"
|
||||
class="brand-logo"
|
||||
/>
|
||||
<span class="brand-name">Sky Art Shop</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="navbar-menu">
|
||||
<ul class="nav-menu-list">
|
||||
<li class="nav-item">
|
||||
<a href="/home.html" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop.html" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio.html" class="nav-link">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about.html" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog.html" class="nav-link">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact.html" class="nav-link">Contact</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="navbar-actions">
|
||||
<div class="action-item wishlist-dropdown-wrapper">
|
||||
<button
|
||||
class="action-btn"
|
||||
id="wishlistToggle"
|
||||
aria-label="Wishlist"
|
||||
>
|
||||
<i class="bi bi-heart"></i>
|
||||
<span class="action-badge" id="wishlistCount">0</span>
|
||||
</button>
|
||||
<div class="action-dropdown wishlist-dropdown" id="wishlistPanel">
|
||||
<div class="dropdown-head">
|
||||
<h3>My Wishlist</h3>
|
||||
<button class="dropdown-close" id="wishlistClose">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-body" id="wishlistContent">
|
||||
<p class="empty-state">Your wishlist is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<a href="/shop.html" class="btn-outline">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-item cart-dropdown-wrapper">
|
||||
<button
|
||||
class="action-btn"
|
||||
id="cartToggle"
|
||||
aria-label="Shopping Cart"
|
||||
>
|
||||
<i class="bi bi-cart3"></i>
|
||||
<span class="action-badge" id="cartCount">0</span>
|
||||
</button>
|
||||
<div class="action-dropdown cart-dropdown" id="cartPanel">
|
||||
<div class="dropdown-head">
|
||||
<h3>Shopping Cart</h3>
|
||||
<button class="dropdown-close" id="cartClose">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-body" id="cartContent">
|
||||
<p class="empty-state">Your cart is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<div class="cart-summary">
|
||||
<span class="summary-label">Subtotal:</span>
|
||||
<span class="summary-value" id="cartSubtotal">$0.00</span>
|
||||
</div>
|
||||
<a href="/checkout.html" class="btn-primary-full"
|
||||
>Proceed to Checkout</a
|
||||
>
|
||||
<a href="/shop.html" class="btn-text">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="mobile-toggle" id="mobileMenuToggle" aria-label="Menu">
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="navbar-menu">
|
||||
<ul class="nav-menu-list">
|
||||
<li class="nav-item"><a href="/home.html" class="nav-link">Home</a></li>
|
||||
<li class="nav-item"><a href="/shop.html" class="nav-link">Shop</a></li>
|
||||
<li class="nav-item"><a href="/portfolio.html" class="nav-link">Portfolio</a></li>
|
||||
<li class="nav-item"><a href="/about.html" class="nav-link">About</a></li>
|
||||
<li class="nav-item"><a href="/blog.html" class="nav-link">Blog</a></li>
|
||||
<li class="nav-item"><a href="/contact.html" class="nav-link">Contact</a></li>
|
||||
|
||||
<div class="mobile-menu" id="mobileMenu">
|
||||
<div class="mobile-menu-header">
|
||||
<span class="mobile-brand">Sky Art Shop</span>
|
||||
<button class="mobile-close" id="mobileMenuClose">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<ul class="mobile-menu-list">
|
||||
<li><a href="/home.html" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop.html" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio.html" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about.html" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog.html" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact.html" class="mobile-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="navbar-actions">
|
||||
<div class="action-item wishlist-dropdown-wrapper">
|
||||
<button class="action-btn" id="wishlistToggle" aria-label="Wishlist">
|
||||
<i class="bi bi-heart"></i>
|
||||
<span class="action-badge" id="wishlistCount">0</span>
|
||||
</button>
|
||||
<div class="action-dropdown wishlist-dropdown" id="wishlistPanel">
|
||||
<div class="dropdown-head">
|
||||
<h3>My Wishlist</h3>
|
||||
<button class="dropdown-close" id="wishlistClose"><i class="bi bi-x-lg"></i></button>
|
||||
</div>
|
||||
<div class="dropdown-body" id="wishlistContent">
|
||||
<p class="empty-state">Your wishlist is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<a href="/shop.html" class="btn-outline">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-item cart-dropdown-wrapper">
|
||||
<button class="action-btn" id="cartToggle" aria-label="Shopping Cart">
|
||||
<i class="bi bi-cart3"></i>
|
||||
<span class="action-badge" id="cartCount">0</span>
|
||||
</button>
|
||||
<div class="action-dropdown cart-dropdown" id="cartPanel">
|
||||
<div class="dropdown-head">
|
||||
<h3>Shopping Cart</h3>
|
||||
<button class="dropdown-close" id="cartClose"><i class="bi bi-x-lg"></i></button>
|
||||
</div>
|
||||
<div class="dropdown-body" id="cartContent">
|
||||
<p class="empty-state">Your cart is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<div class="cart-summary">
|
||||
<span class="summary-label">Subtotal:</span>
|
||||
<span class="summary-value" id="cartSubtotal">$0.00</span>
|
||||
</div>
|
||||
<a href="/checkout.html" class="btn-primary-full">Proceed to Checkout</a>
|
||||
<a href="/shop.html" class="btn-text">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="mobile-toggle" id="mobileMenuToggle" aria-label="Menu">
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mobile-menu" id="mobileMenu">
|
||||
<div class="mobile-menu-header">
|
||||
<span class="mobile-brand">Sky Art Shop</span>
|
||||
<button class="mobile-close" id="mobileMenuClose"><i class="bi bi-x-lg"></i></button>
|
||||
</div>
|
||||
<ul class="mobile-menu-list">
|
||||
<li><a href="/home.html" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop.html" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio.html" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about.html" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog.html" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact.html" class="mobile-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
|
||||
<section class="about-hero">
|
||||
<div class="container">
|
||||
<h1>About Sky Art Shop</h1>
|
||||
<p class="hero-subtitle">Your creative journey starts here</p>
|
||||
</div>
|
||||
<div class="container">
|
||||
<h1>Blog</h1>
|
||||
<p class="hero-subtitle">Inspiration, tips, and creative ideas</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="about-content">
|
||||
<div class="container">
|
||||
<div class="about-layout">
|
||||
<div class="about-main-content">
|
||||
<div class="about-text">
|
||||
<h2>Our Story</h2>
|
||||
<p>Sky Art Shop specializes in scrapbooking, journaling, cardmaking, and collaging stationery. We are passionate about helping people express their creativity and preserve their memories.</p>
|
||||
<p>Our mission is to promote mental health and wellness through creative art activities. We believe that crafting is more than just a hobby—it's a therapeutic journey that brings joy, mindfulness, and self-expression.</p>
|
||||
|
||||
<h2>What We Offer</h2>
|
||||
<p>Our carefully curated collection includes:</p>
|
||||
<ul>
|
||||
<li>Washi tape in various designs and patterns</li>
|
||||
<li>Unique stickers for journaling and scrapbooking</li>
|
||||
<li>High-quality journals and notebooks</li>
|
||||
<li>Card making supplies and kits</li>
|
||||
<li>Collage materials and ephemera</li>
|
||||
<li>Creative tools and accessories</li>
|
||||
</ul>
|
||||
|
||||
<h2>Why Choose Us</h2>
|
||||
<p>We hand-select every item in our store to ensure the highest quality and uniqueness. Whether you're a seasoned crafter or just starting your creative journey, we have something special for everyone.</p>
|
||||
<p>Join our community of creative minds and let your imagination soar!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section class="blog-section" style="padding: 60px 0; background: #f8f9fa">
|
||||
<div class="container">
|
||||
<div id="loadingMessage" style="text-align: center; padding: 40px">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p style="margin-top: 15px; color: #666">Loading blog posts...</p>
|
||||
</div>
|
||||
<div
|
||||
id="blogGrid"
|
||||
style="
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 30px;
|
||||
"
|
||||
></div>
|
||||
<div
|
||||
id="noPosts"
|
||||
style="display: none; text-align: center; padding: 40px; color: #666"
|
||||
>
|
||||
<i
|
||||
class="bi bi-journal-text"
|
||||
style="font-size: 48px; color: #ccc; margin-bottom: 15px"
|
||||
></i>
|
||||
<p>No blog posts available at the moment.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-content">
|
||||
<div class="footer-brand">
|
||||
<h2>Sky Art Shop</h2>
|
||||
<p>Follow Us</p>
|
||||
<div class="social-links">
|
||||
<a href="#instagram" aria-label="Instagram"><i class="bi bi-instagram"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-links">
|
||||
<h3>Quick Links</h3>
|
||||
<ul>
|
||||
<li><a href="/shop.html">Shop</a></li>
|
||||
<li><a href="/about.html">About</a></li>
|
||||
<li><a href="/contact.html">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 by Sky Art Shop. All rights reserved.</p>
|
||||
<div class="container">
|
||||
<div class="footer-content">
|
||||
<div class="footer-brand">
|
||||
<h2>Sky Art Shop</h2>
|
||||
<p>Follow Us</p>
|
||||
<div class="social-links">
|
||||
<a href="#instagram" aria-label="Instagram"
|
||||
><i class="bi bi-instagram"></i
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-links">
|
||||
<h3>Quick Links</h3>
|
||||
<ul>
|
||||
<li><a href="/shop.html">Shop</a></li>
|
||||
<li><a href="/about.html">About</a></li>
|
||||
<li><a href="/contact.html">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 by Sky Art Shop. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="/assets/js/main.js"></script>
|
||||
<script src="/assets/js/cart.js"></script>
|
||||
<script src="/assets/js/shopping.js"></script>
|
||||
</body>
|
||||
<script src="/assets/js/shopping.js"></script>
|
||||
<script>
|
||||
// Load blog posts from API
|
||||
async function loadBlog() {
|
||||
try {
|
||||
const response = await fetch("/api/blog/posts");
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const posts = data.posts || [];
|
||||
|
||||
document.getElementById("loadingMessage").style.display = "none";
|
||||
|
||||
if (posts.length === 0) {
|
||||
document.getElementById("noPosts").style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
const grid = document.getElementById("blogGrid");
|
||||
grid.innerHTML = posts
|
||||
.map(
|
||||
(post) => `
|
||||
<article class="blog-card" style="background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); transition: transform 0.3s; cursor: pointer;" onclick="window.location.href='/blog/${
|
||||
post.slug
|
||||
}'">
|
||||
${
|
||||
post.imageurl
|
||||
? `
|
||||
<div class="blog-image" style="position: relative; padding-top: 56.25%; overflow: hidden; background: #f5f5f5;">
|
||||
<img src="${post.imageurl}"
|
||||
alt="${post.title}"
|
||||
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;"
|
||||
loading="lazy" />
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
<div style="padding: 25px;">
|
||||
<div style="display: flex; align-items: center; gap: 15px; margin-bottom: 12px; color: #999; font-size: 13px;">
|
||||
<span><i class="bi bi-calendar"></i> ${new Date(
|
||||
post.createdat
|
||||
).toLocaleDateString()}</span>
|
||||
</div>
|
||||
<h2 style="font-size: 22px; font-weight: 600; margin-bottom: 12px; color: #333; line-height: 1.3;">${
|
||||
post.title
|
||||
}</h2>
|
||||
<p style="color: #666; font-size: 15px; line-height: 1.6; margin-bottom: 20px;">${
|
||||
post.excerpt ||
|
||||
post.content?.substring(0, 150) + "..." ||
|
||||
""
|
||||
}</p>
|
||||
<a href="/blog/${
|
||||
post.slug
|
||||
}" style="display: inline-flex; align-items: center; color: #667eea; font-weight: 500; text-decoration: none; transition: gap 0.3s;" onclick="event.stopPropagation()">
|
||||
Read More <i class="bi bi-arrow-right" style="margin-left: 8px;"></i>
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
} else {
|
||||
document.getElementById("loadingMessage").style.display = "none";
|
||||
document.getElementById("noPosts").style.display = "block";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading blog:", error);
|
||||
document.getElementById("loadingMessage").innerHTML =
|
||||
'<p style="color: #dc3545;">Error loading blog posts. Please try again later.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
loadBlog();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="Sky Art Shop - Scrapbooking, journaling, cardmaking, and collaging stationery." />
|
||||
<title>Home - Sky Art Shop</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
<style>
|
||||
/* Include navbar styles inline for now */
|
||||
<?php include '/var/www/skyartshop/components/navbar.html'; ?>
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Modern Navigation -->
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home.html" class="brand-link">
|
||||
<img src="/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg" alt="Sky Art Shop Logo" class="brand-logo" />
|
||||
<span class="brand-name">Sky Art Shop</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="navbar-menu">
|
||||
<ul class="nav-menu-list">
|
||||
<li class="nav-item"><a href="/home.html" class="nav-link active">Home</a></li>
|
||||
<li class="nav-item"><a href="/shop.html" class="nav-link">Shop</a></li>
|
||||
<li class="nav-item"><a href="/portfolio.html" class="nav-link">Portfolio</a></li>
|
||||
<li class="nav-item"><a href="/about.html" class="nav-link">About</a></li>
|
||||
<li class="nav-item"><a href="/blog.html" class="nav-link">Blog</a></li>
|
||||
<li class="nav-item"><a href="/contact.html" class="nav-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="navbar-actions">
|
||||
<div class="action-item wishlist-dropdown-wrapper">
|
||||
<button class="action-btn" id="wishlistToggle" aria-label="Wishlist">
|
||||
<i class="bi bi-heart"></i>
|
||||
<span class="action-badge" id="wishlistCount">0</span>
|
||||
</button>
|
||||
<div class="action-dropdown wishlist-dropdown" id="wishlistPanel">
|
||||
<div class="dropdown-head">
|
||||
<h3>My Wishlist</h3>
|
||||
<button class="dropdown-close" id="wishlistClose"><i class="bi bi-x-lg"></i></button>
|
||||
</div>
|
||||
<div class="dropdown-body" id="wishlistContent">
|
||||
<p class="empty-state">Your wishlist is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<a href="/shop.html" class="btn-outline">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-item cart-dropdown-wrapper">
|
||||
<button class="action-btn" id="cartToggle" aria-label="Shopping Cart">
|
||||
<i class="bi bi-cart3"></i>
|
||||
<span class="action-badge" id="cartCount">0</span>
|
||||
</button>
|
||||
<div class="action-dropdown cart-dropdown" id="cartPanel">
|
||||
<div class="dropdown-head">
|
||||
<h3>Shopping Cart</h3>
|
||||
<button class="dropdown-close" id="cartClose"><i class="bi bi-x-lg"></i></button>
|
||||
</div>
|
||||
<div class="dropdown-body" id="cartContent">
|
||||
<p class="empty-state">Your cart is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<div class="cart-summary">
|
||||
<span class="summary-label">Subtotal:</span>
|
||||
<span class="summary-value" id="cartSubtotal">$0.00</span>
|
||||
</div>
|
||||
<a href="/checkout.html" class="btn-primary-full">Proceed to Checkout</a>
|
||||
<a href="/shop.html" class="btn-text">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="mobile-toggle" id="mobileMenuToggle" aria-label="Menu">
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mobile-menu" id="mobileMenu">
|
||||
<div class="mobile-menu-header">
|
||||
<span class="mobile-brand">Sky Art Shop</span>
|
||||
<button class="mobile-close" id="mobileMenuClose"><i class="bi bi-x-lg"></i></button>
|
||||
</div>
|
||||
<ul class="mobile-menu-list">
|
||||
<li><a href="/home.html" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop.html" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio.html" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about.html" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog.html" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact.html" class="mobile-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<section class="hero" style="padding: 80px 24px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; text-align: center;">
|
||||
<div class="hero-content" style="max-width: 800px; margin: 0 auto;">
|
||||
<h2 style="font-size: 48px; font-weight: 700; margin-bottom: 16px; line-height: 1.2;">Welcome to Sky Art Shop</h2>
|
||||
<p style="font-size: 20px; margin-bottom: 24px; opacity: 0.95;">Your destination for creative stationery and supplies</p>
|
||||
<div class="hero-description" style="margin-bottom: 32px;">
|
||||
<p style="font-size: 16px; opacity: 0.9; line-height: 1.6;">
|
||||
Discover our curated collection of scrapbooking, journaling, cardmaking, and collaging supplies.
|
||||
Express your creativity and bring your artistic vision to life.
|
||||
</p>
|
||||
</div>
|
||||
<a href="/shop.html" class="btn" style="display: inline-block; padding: 14px 32px; background: white; color: #667eea; font-weight: 600; border-radius: 8px; text-decoration: none; transition: transform 0.2s;">Shop Now</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Featured Products Section -->
|
||||
<section class="collection" style="padding: 80px 24px; background: #f9fafb;">
|
||||
<div class="container" style="max-width: 1200px; margin: 0 auto;">
|
||||
<h2 style="text-align: center; font-size: 36px; font-weight: 700; color: #1a1a1a; margin-bottom: 12px;">Featured Products</h2>
|
||||
<p style="text-align: center; color: #6b7280; margin-bottom: 48px; font-size: 16px;">Discover our most popular items</p>
|
||||
<div class="products-grid" id="featuredProducts" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 24px; margin-bottom: 48px;">
|
||||
<div style="text-align: center; padding: 40px; color: #9ca3af;">Loading products...</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<a href="/shop.html" class="btn" style="display: inline-block; padding: 12px 28px; background: #6b46c1; color: white; font-weight: 600; border-radius: 8px; text-decoration: none;">View All Products</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer style="padding: 40px 24px; background: #1f2937; color: white; text-align: center;">
|
||||
<div style="max-width: 1200px; margin: 0 auto;">
|
||||
<h3 style="font-size: 24px; font-weight: 600; margin-bottom: 16px;">Sky Art Shop</h3>
|
||||
<p style="margin-bottom: 20px; color: #9ca3af;">Follow Us</p>
|
||||
<div style="display: flex; gap: 16px; justify-content: center; margin-bottom: 24px;">
|
||||
<a href="#" style="color: white; font-size: 24px;"><i class="bi bi-instagram"></i></a>
|
||||
<a href="#" style="color: white; font-size: 24px;"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#" style="color: white; font-size: 24px;"><i class="bi bi-twitter"></i></a>
|
||||
</div>
|
||||
<p style="color: #9ca3af; font-size: 14px;">© 2025 Sky Art Shop. All rights reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Load Navbar Styles -->
|
||||
<script>
|
||||
// Inject navbar styles
|
||||
const navStyles = document.createElement('style');
|
||||
navStyles.textContent = `
|
||||
${document.querySelector('style').textContent}
|
||||
`;
|
||||
</script>
|
||||
|
||||
<script src="/assets/js/shopping.js"></script>
|
||||
<script>
|
||||
// Load featured products
|
||||
async function loadFeaturedProducts() {
|
||||
try {
|
||||
const response = await fetch('/api/products/featured?limit=4');
|
||||
const data = await response.json();
|
||||
|
||||
const container = document.getElementById('featuredProducts');
|
||||
if (data.success && data.products && data.products.length > 0) {
|
||||
container.innerHTML = data.products.map(product => `
|
||||
<div class="product-card" style="background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); transition: transform 0.2s, box-shadow 0.2s;">
|
||||
<div class="product-image" style="position: relative; padding-top: 100%; overflow: hidden; background: #f3f4f6;">
|
||||
<img src="${product.imageurl || '/assets/images/placeholder.jpg'}"
|
||||
alt="${product.name}"
|
||||
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;"
|
||||
onerror="this.src='/assets/images/placeholder.jpg'" />
|
||||
</div>
|
||||
<div style="padding: 20px;">
|
||||
<h3 style="font-size: 16px; font-weight: 600; color: #1a1a1a; margin: 0 0 8px 0; line-height: 1.4;">${product.name}</h3>
|
||||
<p style="font-size: 20px; font-weight: 700; color: #6b46c1; margin: 0 0 16px 0;">$${parseFloat(product.price).toFixed(2)}</p>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button onclick='shoppingManager.addToCart(${JSON.stringify(product)})'
|
||||
style="flex: 1; padding: 10px; background: #6b46c1; color: white; border: none; border-radius: 6px; font-weight: 500; cursor: pointer; transition: background 0.2s;">
|
||||
<i class="bi bi-cart-plus"></i> Add to Cart
|
||||
</button>
|
||||
<button onclick='shoppingManager.addToWishlist(${JSON.stringify(product)})'
|
||||
style="width: 44px; padding: 10px; background: transparent; color: #6b46c1; border: 1px solid #6b46c1; border-radius: 6px; cursor: pointer; transition: all 0.2s;">
|
||||
<i class="bi bi-heart"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
} else {
|
||||
container.innerHTML = '<p style="text-align: center; color: #9ca3af; padding: 40px;">No products available</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading products:', error);
|
||||
document.getElementById('featuredProducts').innerHTML = '<p style="text-align: center; color: #ef4444; padding: 40px;">Error loading products</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// Load products on page load
|
||||
loadFeaturedProducts();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,47 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>About - Sky Art Shop</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="/assets/css/main.css" />
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
</head>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Modern Navigation -->
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home.html" class="brand-link">
|
||||
<img src="/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg" alt="Sky Art Shop Logo" class="brand-logo" />
|
||||
<img
|
||||
src="/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg"
|
||||
alt="Sky Art Shop Logo"
|
||||
class="brand-logo"
|
||||
/>
|
||||
<span class="brand-name">Sky Art Shop</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="navbar-menu">
|
||||
<ul class="nav-menu-list">
|
||||
<li class="nav-item"><a href="/home.html" class="nav-link">Home</a></li>
|
||||
<li class="nav-item"><a href="/shop.html" class="nav-link">Shop</a></li>
|
||||
<li class="nav-item"><a href="/portfolio.html" class="nav-link active">Portfolio</a></li>
|
||||
<li class="nav-item"><a href="/about.html" class="nav-link">About</a></li>
|
||||
<li class="nav-item"><a href="/blog.html" class="nav-link">Blog</a></li>
|
||||
<li class="nav-item"><a href="/contact.html" class="nav-link">Contact</a></li>
|
||||
<li class="nav-item">
|
||||
<a href="/home.html" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop.html" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio.html" class="nav-link active">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about.html" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog.html" class="nav-link">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact.html" class="nav-link">Contact</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="navbar-actions">
|
||||
<div class="action-item wishlist-dropdown-wrapper">
|
||||
<button class="action-btn" id="wishlistToggle" aria-label="Wishlist">
|
||||
<button
|
||||
class="action-btn"
|
||||
id="wishlistToggle"
|
||||
aria-label="Wishlist"
|
||||
>
|
||||
<i class="bi bi-heart"></i>
|
||||
<span class="action-badge" id="wishlistCount">0</span>
|
||||
</button>
|
||||
<div class="action-dropdown wishlist-dropdown" id="wishlistPanel">
|
||||
<div class="dropdown-head">
|
||||
<h3>My Wishlist</h3>
|
||||
<button class="dropdown-close" id="wishlistClose"><i class="bi bi-x-lg"></i></button>
|
||||
<button class="dropdown-close" id="wishlistClose">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-body" id="wishlistContent">
|
||||
<p class="empty-state">Your wishlist is empty</p>
|
||||
@@ -51,16 +79,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="action-item cart-dropdown-wrapper">
|
||||
<button class="action-btn" id="cartToggle" aria-label="Shopping Cart">
|
||||
<button
|
||||
class="action-btn"
|
||||
id="cartToggle"
|
||||
aria-label="Shopping Cart"
|
||||
>
|
||||
<i class="bi bi-cart3"></i>
|
||||
<span class="action-badge" id="cartCount">0</span>
|
||||
</button>
|
||||
<div class="action-dropdown cart-dropdown" id="cartPanel">
|
||||
<div class="dropdown-head">
|
||||
<h3>Shopping Cart</h3>
|
||||
<button class="dropdown-close" id="cartClose"><i class="bi bi-x-lg"></i></button>
|
||||
<button class="dropdown-close" id="cartClose">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-body" id="cartContent">
|
||||
<p class="empty-state">Your cart is empty</p>
|
||||
@@ -70,12 +104,14 @@
|
||||
<span class="summary-label">Subtotal:</span>
|
||||
<span class="summary-value" id="cartSubtotal">$0.00</span>
|
||||
</div>
|
||||
<a href="/checkout.html" class="btn-primary-full">Proceed to Checkout</a>
|
||||
<a href="/checkout.html" class="btn-primary-full"
|
||||
>Proceed to Checkout</a
|
||||
>
|
||||
<a href="/shop.html" class="btn-text">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<button class="mobile-toggle" id="mobileMenuToggle" aria-label="Menu">
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
@@ -83,11 +119,13 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mobile-menu" id="mobileMenu">
|
||||
<div class="mobile-menu-header">
|
||||
<span class="mobile-brand">Sky Art Shop</span>
|
||||
<button class="mobile-close" id="mobileMenuClose"><i class="bi bi-x-lg"></i></button>
|
||||
<button class="mobile-close" id="mobileMenuClose">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<ul class="mobile-menu-list">
|
||||
<li><a href="/home.html" class="mobile-link">Home</a></li>
|
||||
@@ -99,157 +137,262 @@
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home.html" class="brand-link">
|
||||
<img src="/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg" alt="Sky Art Shop Logo" class="brand-logo" />
|
||||
<span class="brand-name">Sky Art Shop</span>
|
||||
</a>
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home.html" class="brand-link">
|
||||
<img
|
||||
src="/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg"
|
||||
alt="Sky Art Shop Logo"
|
||||
class="brand-logo"
|
||||
/>
|
||||
<span class="brand-name">Sky Art Shop</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="navbar-menu">
|
||||
<ul class="nav-menu-list">
|
||||
<li class="nav-item">
|
||||
<a href="/home.html" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop.html" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio.html" class="nav-link">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about.html" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog.html" class="nav-link">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact.html" class="nav-link">Contact</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="navbar-actions">
|
||||
<div class="action-item wishlist-dropdown-wrapper">
|
||||
<button
|
||||
class="action-btn"
|
||||
id="wishlistToggle"
|
||||
aria-label="Wishlist"
|
||||
>
|
||||
<i class="bi bi-heart"></i>
|
||||
<span class="action-badge" id="wishlistCount">0</span>
|
||||
</button>
|
||||
<div class="action-dropdown wishlist-dropdown" id="wishlistPanel">
|
||||
<div class="dropdown-head">
|
||||
<h3>My Wishlist</h3>
|
||||
<button class="dropdown-close" id="wishlistClose">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-body" id="wishlistContent">
|
||||
<p class="empty-state">Your wishlist is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<a href="/shop.html" class="btn-outline">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-item cart-dropdown-wrapper">
|
||||
<button
|
||||
class="action-btn"
|
||||
id="cartToggle"
|
||||
aria-label="Shopping Cart"
|
||||
>
|
||||
<i class="bi bi-cart3"></i>
|
||||
<span class="action-badge" id="cartCount">0</span>
|
||||
</button>
|
||||
<div class="action-dropdown cart-dropdown" id="cartPanel">
|
||||
<div class="dropdown-head">
|
||||
<h3>Shopping Cart</h3>
|
||||
<button class="dropdown-close" id="cartClose">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-body" id="cartContent">
|
||||
<p class="empty-state">Your cart is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<div class="cart-summary">
|
||||
<span class="summary-label">Subtotal:</span>
|
||||
<span class="summary-value" id="cartSubtotal">$0.00</span>
|
||||
</div>
|
||||
<a href="/checkout.html" class="btn-primary-full"
|
||||
>Proceed to Checkout</a
|
||||
>
|
||||
<a href="/shop.html" class="btn-text">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="mobile-toggle" id="mobileMenuToggle" aria-label="Menu">
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="navbar-menu">
|
||||
<ul class="nav-menu-list">
|
||||
<li class="nav-item"><a href="/home.html" class="nav-link">Home</a></li>
|
||||
<li class="nav-item"><a href="/shop.html" class="nav-link">Shop</a></li>
|
||||
<li class="nav-item"><a href="/portfolio.html" class="nav-link">Portfolio</a></li>
|
||||
<li class="nav-item"><a href="/about.html" class="nav-link">About</a></li>
|
||||
<li class="nav-item"><a href="/blog.html" class="nav-link">Blog</a></li>
|
||||
<li class="nav-item"><a href="/contact.html" class="nav-link">Contact</a></li>
|
||||
|
||||
<div class="mobile-menu" id="mobileMenu">
|
||||
<div class="mobile-menu-header">
|
||||
<span class="mobile-brand">Sky Art Shop</span>
|
||||
<button class="mobile-close" id="mobileMenuClose">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<ul class="mobile-menu-list">
|
||||
<li><a href="/home.html" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop.html" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio.html" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about.html" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog.html" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact.html" class="mobile-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="navbar-actions">
|
||||
<div class="action-item wishlist-dropdown-wrapper">
|
||||
<button class="action-btn" id="wishlistToggle" aria-label="Wishlist">
|
||||
<i class="bi bi-heart"></i>
|
||||
<span class="action-badge" id="wishlistCount">0</span>
|
||||
</button>
|
||||
<div class="action-dropdown wishlist-dropdown" id="wishlistPanel">
|
||||
<div class="dropdown-head">
|
||||
<h3>My Wishlist</h3>
|
||||
<button class="dropdown-close" id="wishlistClose"><i class="bi bi-x-lg"></i></button>
|
||||
</div>
|
||||
<div class="dropdown-body" id="wishlistContent">
|
||||
<p class="empty-state">Your wishlist is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<a href="/shop.html" class="btn-outline">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-item cart-dropdown-wrapper">
|
||||
<button class="action-btn" id="cartToggle" aria-label="Shopping Cart">
|
||||
<i class="bi bi-cart3"></i>
|
||||
<span class="action-badge" id="cartCount">0</span>
|
||||
</button>
|
||||
<div class="action-dropdown cart-dropdown" id="cartPanel">
|
||||
<div class="dropdown-head">
|
||||
<h3>Shopping Cart</h3>
|
||||
<button class="dropdown-close" id="cartClose"><i class="bi bi-x-lg"></i></button>
|
||||
</div>
|
||||
<div class="dropdown-body" id="cartContent">
|
||||
<p class="empty-state">Your cart is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<div class="cart-summary">
|
||||
<span class="summary-label">Subtotal:</span>
|
||||
<span class="summary-value" id="cartSubtotal">$0.00</span>
|
||||
</div>
|
||||
<a href="/checkout.html" class="btn-primary-full">Proceed to Checkout</a>
|
||||
<a href="/shop.html" class="btn-text">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="mobile-toggle" id="mobileMenuToggle" aria-label="Menu">
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mobile-menu" id="mobileMenu">
|
||||
<div class="mobile-menu-header">
|
||||
<span class="mobile-brand">Sky Art Shop</span>
|
||||
<button class="mobile-close" id="mobileMenuClose"><i class="bi bi-x-lg"></i></button>
|
||||
</div>
|
||||
<ul class="mobile-menu-list">
|
||||
<li><a href="/home.html" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop.html" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio.html" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about.html" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog.html" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact.html" class="mobile-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
|
||||
<section class="about-hero">
|
||||
<div class="container">
|
||||
<h1>About Sky Art Shop</h1>
|
||||
<p class="hero-subtitle">Your creative journey starts here</p>
|
||||
</div>
|
||||
<div class="container">
|
||||
<h1>Portfolio</h1>
|
||||
<p class="hero-subtitle">Explore our creative projects and artwork</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="about-content">
|
||||
<div class="container">
|
||||
<div class="about-layout">
|
||||
<div class="about-main-content">
|
||||
<div class="about-text">
|
||||
<h2>Our Story</h2>
|
||||
<p>Sky Art Shop specializes in scrapbooking, journaling, cardmaking, and collaging stationery. We are passionate about helping people express their creativity and preserve their memories.</p>
|
||||
<p>Our mission is to promote mental health and wellness through creative art activities. We believe that crafting is more than just a hobby—it's a therapeutic journey that brings joy, mindfulness, and self-expression.</p>
|
||||
|
||||
<h2>What We Offer</h2>
|
||||
<p>Our carefully curated collection includes:</p>
|
||||
<ul>
|
||||
<li>Washi tape in various designs and patterns</li>
|
||||
<li>Unique stickers for journaling and scrapbooking</li>
|
||||
<li>High-quality journals and notebooks</li>
|
||||
<li>Card making supplies and kits</li>
|
||||
<li>Collage materials and ephemera</li>
|
||||
<li>Creative tools and accessories</li>
|
||||
</ul>
|
||||
|
||||
<h2>Why Choose Us</h2>
|
||||
<p>We hand-select every item in our store to ensure the highest quality and uniqueness. Whether you're a seasoned crafter or just starting your creative journey, we have something special for everyone.</p>
|
||||
<p>Join our community of creative minds and let your imagination soar!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section
|
||||
class="portfolio-section"
|
||||
style="padding: 60px 0; background: #f8f9fa"
|
||||
>
|
||||
<div class="container">
|
||||
<div id="loadingMessage" style="text-align: center; padding: 40px">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p style="margin-top: 15px; color: #666">
|
||||
Loading portfolio projects...
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
id="portfolioGrid"
|
||||
class="products-grid"
|
||||
style="
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 30px;
|
||||
"
|
||||
></div>
|
||||
<div
|
||||
id="noProjects"
|
||||
style="display: none; text-align: center; padding: 40px; color: #666"
|
||||
>
|
||||
<i
|
||||
class="bi bi-images"
|
||||
style="font-size: 48px; color: #ccc; margin-bottom: 15px"
|
||||
></i>
|
||||
<p>No portfolio projects available at the moment.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-content">
|
||||
<div class="footer-brand">
|
||||
<h2>Sky Art Shop</h2>
|
||||
<p>Follow Us</p>
|
||||
<div class="social-links">
|
||||
<a href="#instagram" aria-label="Instagram"><i class="bi bi-instagram"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-links">
|
||||
<h3>Quick Links</h3>
|
||||
<ul>
|
||||
<li><a href="/shop.html">Shop</a></li>
|
||||
<li><a href="/about.html">About</a></li>
|
||||
<li><a href="/contact.html">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 by Sky Art Shop. All rights reserved.</p>
|
||||
<div class="container">
|
||||
<div class="footer-content">
|
||||
<div class="footer-brand">
|
||||
<h2>Sky Art Shop</h2>
|
||||
<p>Follow Us</p>
|
||||
<div class="social-links">
|
||||
<a href="#instagram" aria-label="Instagram"
|
||||
><i class="bi bi-instagram"></i
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-links">
|
||||
<h3>Quick Links</h3>
|
||||
<ul>
|
||||
<li><a href="/shop.html">Shop</a></li>
|
||||
<li><a href="/about.html">About</a></li>
|
||||
<li><a href="/contact.html">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 by Sky Art Shop. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="/assets/js/main.js"></script>
|
||||
<script src="/assets/js/cart.js"></script>
|
||||
<script src="/assets/js/shopping.js"></script>
|
||||
</body>
|
||||
<script src="/assets/js/shopping.js"></script>
|
||||
<script>
|
||||
// Load portfolio projects from API
|
||||
async function loadPortfolio() {
|
||||
try {
|
||||
const response = await fetch("/api/portfolio/projects");
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const projects = data.projects || [];
|
||||
|
||||
document.getElementById("loadingMessage").style.display = "none";
|
||||
|
||||
if (projects.length === 0) {
|
||||
document.getElementById("noProjects").style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
const grid = document.getElementById("portfolioGrid");
|
||||
grid.innerHTML = projects
|
||||
.map(
|
||||
(project) => `
|
||||
<div class="product-card" style="background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); transition: transform 0.3s;">
|
||||
<div class="product-image" style="position: relative; padding-top: 100%; overflow: hidden; background: #f5f5f5;">
|
||||
<img src="${
|
||||
project.imageurl || "/assets/images/placeholder.jpg"
|
||||
}"
|
||||
alt="${project.title}"
|
||||
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;"
|
||||
loading="lazy" />
|
||||
${
|
||||
project.category
|
||||
? `<span style="position: absolute; top: 10px; right: 10px; background: rgba(102, 126, 234, 0.9); color: white; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 500;">${project.category}</span>`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
<div style="padding: 20px;">
|
||||
<h3 style="font-size: 18px; font-weight: 600; margin-bottom: 10px; color: #333;">${
|
||||
project.title
|
||||
}</h3>
|
||||
<p style="color: #666; font-size: 14px; line-height: 1.6; margin: 0;">${
|
||||
project.description || ""
|
||||
}</p>
|
||||
<div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #eee; color: #999; font-size: 12px;">
|
||||
<i class="bi bi-calendar"></i> ${new Date(
|
||||
project.createdat
|
||||
).toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
} else {
|
||||
document.getElementById("loadingMessage").style.display = "none";
|
||||
document.getElementById("noProjects").style.display = "block";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading portfolio:", error);
|
||||
document.getElementById("loadingMessage").innerHTML =
|
||||
'<p style="color: #dc3545;">Error loading portfolio projects. Please try again later.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
loadPortfolio();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user