updateweb

This commit is contained in:
Local Server
2025-12-14 01:54:40 -06:00
parent dce6460994
commit 61929a5daf
454 changed files with 12193 additions and 42002 deletions

242
website/admin/blog.html Normal file
View 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>

View 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);
}

View File

@@ -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
View 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
View 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
View 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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;",
};
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);
}

View 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
View 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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;",
};
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);
}

View 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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;",
};
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);
}

View 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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;",
};
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);
}

View 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
View 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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;",
};
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);
}

View 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
View 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
View 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>

View 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
View 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
View 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

View File

@@ -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>

View File

@@ -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;">&copy; 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>

View File

@@ -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>