webupdate
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -18,10 +18,11 @@
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="stylesheet" href="/admin/css/admin-style.css" />
|
||||
<link rel="stylesheet" href="/admin/css/media-library.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<div class="sidebar-brand">Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard"
|
||||
@@ -37,9 +38,7 @@
|
||||
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
<a href="/admin/portfolio"><i class="bi bi-easel"></i> Portfolio</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog" class="active"
|
||||
@@ -65,6 +64,11 @@
|
||||
<li>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/customers"
|
||||
><i class="bi bi-person-hearts"></i> Customers</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -156,115 +160,272 @@
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body" style="max-height: 75vh; overflow-y: auto">
|
||||
<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>
|
||||
<div
|
||||
id="postContentEditor"
|
||||
style="
|
||||
height: 400px;
|
||||
background: white;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
"
|
||||
>
|
||||
<style>
|
||||
#postContentEditor .ql-container {
|
||||
height: calc(400px - 42px);
|
||||
overflow-y: auto;
|
||||
font-size: 16px;
|
||||
}
|
||||
#postContentEditor .ql-editor {
|
||||
min-height: 100%;
|
||||
}
|
||||
</style>
|
||||
<!-- Basic Info Section -->
|
||||
<div class="blog-section">
|
||||
<h6 class="blog-section-title">
|
||||
<i class="bi bi-info-circle"></i> Basic Information
|
||||
</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-8 mb-3">
|
||||
<label for="postTitle" class="form-label">Title *</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="postTitle"
|
||||
required
|
||||
placeholder="Enter blog title"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="postSlug" class="form-label">Slug *</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="postSlug"
|
||||
required
|
||||
placeholder="url-friendly-slug"
|
||||
/>
|
||||
<small class="text-muted">Auto-generated from title</small>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" id="postContent" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Featured Image</label>
|
||||
<input type="hidden" id="postFeaturedImage" />
|
||||
<div
|
||||
id="featuredImagePreview"
|
||||
style="margin-bottom: 10px"
|
||||
></div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary btn-sm"
|
||||
onclick="openMediaLibraryForFeaturedImage()"
|
||||
>
|
||||
<i class="bi bi-image"></i> Select from Media Library
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="postMetaTitle" class="form-label"
|
||||
>Meta Title (SEO)</label
|
||||
<div class="mb-3">
|
||||
<label for="postExcerpt" class="form-label"
|
||||
>Short Description / Excerpt</label
|
||||
>
|
||||
<input type="text" class="form-control" id="postMetaTitle" />
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="postExcerpt"
|
||||
rows="2"
|
||||
placeholder="Brief summary for listings and previews"
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="postMetaDescription" class="form-label"
|
||||
>Meta Description (SEO)</label
|
||||
</div>
|
||||
|
||||
<!-- Content Section -->
|
||||
<div class="blog-section">
|
||||
<h6 class="blog-section-title">
|
||||
<i class="bi bi-file-text"></i> Content
|
||||
</h6>
|
||||
<div class="mb-3">
|
||||
<div
|
||||
id="postContentEditor"
|
||||
style="
|
||||
height: 350px;
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
"
|
||||
>
|
||||
<style>
|
||||
#postContentEditor .ql-container {
|
||||
height: calc(350px - 42px);
|
||||
overflow-y: auto;
|
||||
font-size: 16px;
|
||||
}
|
||||
#postContentEditor .ql-editor {
|
||||
min-height: 100%;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
<input type="hidden" id="postContent" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Media Section -->
|
||||
<div class="blog-section">
|
||||
<h6 class="blog-section-title">
|
||||
<i class="bi bi-images"></i> Media
|
||||
</h6>
|
||||
<div class="row">
|
||||
<!-- Featured Image -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Featured Image</label>
|
||||
<input type="hidden" id="postFeaturedImage" />
|
||||
<div
|
||||
id="featuredImagePreview"
|
||||
class="media-preview-box"
|
||||
></div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary btn-sm mt-2"
|
||||
onclick="openMediaLibraryForFeaturedImage()"
|
||||
>
|
||||
<i class="bi bi-image"></i> Select Featured Image
|
||||
</button>
|
||||
</div>
|
||||
<!-- Gallery Images -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Image Gallery</label>
|
||||
<input type="hidden" id="postImages" />
|
||||
<div
|
||||
id="galleryImagesPreview"
|
||||
class="gallery-preview-box"
|
||||
></div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary btn-sm mt-2"
|
||||
onclick="openMediaLibraryForGallery()"
|
||||
>
|
||||
<i class="bi bi-images"></i> Add Images to Gallery
|
||||
</button>
|
||||
<small class="text-muted d-block mt-1"
|
||||
>Add multiple images for slideshow</small
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Video -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Video</label>
|
||||
<input type="hidden" id="postVideoUrl" />
|
||||
<div id="videoPreview" class="video-preview-box"></div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary btn-sm mt-2"
|
||||
onclick="openMediaLibraryForVideo()"
|
||||
>
|
||||
<i class="bi bi-camera-video"></i> Select Video
|
||||
</button>
|
||||
<small class="text-muted d-block mt-1"
|
||||
>Or paste a YouTube/Vimeo URL below</small
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="postMetaDescription"
|
||||
class="form-control form-control-sm mt-2"
|
||||
id="postExternalVideo"
|
||||
placeholder="https://youtube.com/watch?v=..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<!-- Poll Section -->
|
||||
<div class="blog-section">
|
||||
<h6 class="blog-section-title">
|
||||
<i class="bi bi-bar-chart"></i> Poll (Optional)
|
||||
</h6>
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enablePoll"
|
||||
onchange="togglePollSection()"
|
||||
/>
|
||||
<label class="form-check-label" for="enablePoll"
|
||||
>Enable Poll</label
|
||||
>
|
||||
</div>
|
||||
<div id="pollSection" style="display: none">
|
||||
<div class="mb-3">
|
||||
<label for="pollQuestion" class="form-label"
|
||||
>Poll Question</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="pollQuestion"
|
||||
placeholder="What's your favorite...?"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Poll Options</label>
|
||||
<div id="pollOptionsContainer">
|
||||
<div class="input-group mb-2 poll-option-row">
|
||||
<span class="input-group-text">1</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control poll-option-input"
|
||||
placeholder="Option 1"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group mb-2 poll-option-row">
|
||||
<span class="input-group-text">2</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control poll-option-input"
|
||||
placeholder="Option 2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary btn-sm"
|
||||
onclick="addPollOption()"
|
||||
>
|
||||
<i class="bi bi-plus"></i> Add Option
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SEO Section -->
|
||||
<div class="blog-section">
|
||||
<h6 class="blog-section-title">
|
||||
<i class="bi bi-search"></i> SEO Settings
|
||||
</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="postMetaTitle" class="form-label"
|
||||
>Meta Title</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="postMetaTitle"
|
||||
placeholder="SEO title for search engines"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="postMetaDescription" class="form-label"
|
||||
>Meta Description</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="postMetaDescription"
|
||||
placeholder="SEO description for search engines"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Publish Settings -->
|
||||
<div
|
||||
class="blog-section"
|
||||
style="
|
||||
background: #f0fdf4;
|
||||
border: 2px solid #22c55e;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
"
|
||||
>
|
||||
<h6 class="blog-section-title" style="color: #16a34a">
|
||||
<i class="bi bi-globe"></i> Publish Settings
|
||||
</h6>
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="postPublished"
|
||||
checked
|
||||
style="width: 3em; height: 1.5em"
|
||||
/>
|
||||
<label class="form-check-label" for="postPublished">
|
||||
Published (visible on website)
|
||||
<label
|
||||
class="form-check-label"
|
||||
for="postPublished"
|
||||
style="font-size: 1.1rem"
|
||||
>
|
||||
<strong>Published</strong>
|
||||
<span style="color: #64748b">(visible on website)</span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="text-muted small mt-2 mb-0">
|
||||
<i class="bi bi-info-circle"></i> Uncheck to save as draft
|
||||
(won't appear on website)
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -284,10 +445,101 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.blog-section {
|
||||
background: #f8fafc;
|
||||
border-radius: 10px;
|
||||
padding: 1.25rem;
|
||||
margin-bottom: 1.25rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
.blog-section-title {
|
||||
color: #334155;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.blog-section-title i {
|
||||
color: #6366f1;
|
||||
}
|
||||
.media-preview-box,
|
||||
.video-preview-box {
|
||||
min-height: 100px;
|
||||
border: 2px dashed #e2e8f0;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
.media-preview-box img {
|
||||
max-width: 100%;
|
||||
max-height: 150px;
|
||||
object-fit: contain;
|
||||
}
|
||||
.gallery-preview-box {
|
||||
min-height: 100px;
|
||||
border: 2px dashed #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
.gallery-preview-box .gallery-thumb {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.gallery-preview-box .gallery-thumb img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.gallery-preview-box .gallery-thumb .remove-btn {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: rgba(239, 68, 68, 0.9);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.video-preview-box video {
|
||||
max-width: 100%;
|
||||
max-height: 200px;
|
||||
}
|
||||
.video-preview-box .video-placeholder {
|
||||
text-align: center;
|
||||
color: #94a3b8;
|
||||
padding: 20px;
|
||||
}
|
||||
.video-preview-box .video-placeholder i {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Quill Editor JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@1.3.7/dist/quill.min.js"></script>
|
||||
<script src="/admin/js/auth.js"></script>
|
||||
<script src="/admin/js/blog.js?v=8.0"></script>
|
||||
<script src="/admin/js/admin-utils.js"></script>
|
||||
<script src="/admin/js/media-library.js?v=9.1"></script>
|
||||
<script src="/admin/js/blog.js?v=9.1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -221,6 +221,27 @@ body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
/* Table Action Buttons */
|
||||
.table .btn-sm {
|
||||
padding: 6px 10px;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 6px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.table .btn-sm:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.table .btn-sm i {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
/* Actions Column */
|
||||
.table td:last-child {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
@@ -229,18 +250,23 @@ body {
|
||||
transition: all 0.3s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary-gradient);
|
||||
background: var(--primary-color);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #5a6fd6;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@@ -249,30 +275,99 @@ body {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #5a6268;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(108, 117, 125, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: var(--success-color);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background: #218838;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: var(--danger-color);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #c82333;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: var(--warning-color);
|
||||
border: none;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-warning:hover {
|
||||
background: #e0a800;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(255, 193, 7, 0.4);
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background: var(--info-color);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-info:hover {
|
||||
background: #138496;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(23, 162, 184, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Outline Button Variants */
|
||||
.btn-outline-primary {
|
||||
background: transparent;
|
||||
border: 2px solid var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.btn-outline-primary:hover {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-outline-secondary {
|
||||
background: transparent;
|
||||
border: 2px solid #6c757d;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.btn-outline-secondary:hover {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-outline-danger {
|
||||
background: transparent;
|
||||
border: 2px solid var(--danger-color);
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
.btn-outline-danger:hover {
|
||||
background: var(--danger-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-logout {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
@@ -1010,6 +1105,56 @@ body.dark-mode .btn-primary:hover {
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
body.dark-mode .btn-secondary {
|
||||
background: #4a5568;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .btn-secondary:hover {
|
||||
background: #5a6a7d;
|
||||
box-shadow: 0 4px 12px rgba(74, 85, 104, 0.4);
|
||||
}
|
||||
|
||||
body.dark-mode .btn-success {
|
||||
background: #38a169;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .btn-success:hover {
|
||||
background: #2f855a;
|
||||
box-shadow: 0 4px 12px rgba(56, 161, 105, 0.4);
|
||||
}
|
||||
|
||||
body.dark-mode .btn-danger {
|
||||
background: #e53e3e;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .btn-danger:hover {
|
||||
background: #c53030;
|
||||
box-shadow: 0 4px 12px rgba(229, 62, 62, 0.4);
|
||||
}
|
||||
|
||||
body.dark-mode .btn-warning {
|
||||
background: #d69e2e;
|
||||
color: #1a202c;
|
||||
}
|
||||
|
||||
body.dark-mode .btn-warning:hover {
|
||||
background: #b7791f;
|
||||
box-shadow: 0 4px 12px rgba(214, 158, 46, 0.4);
|
||||
}
|
||||
|
||||
body.dark-mode .btn-info {
|
||||
background: #3182ce;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .btn-info:hover {
|
||||
background: #2b6cb0;
|
||||
box-shadow: 0 4px 12px rgba(49, 130, 206, 0.4);
|
||||
}
|
||||
|
||||
body.dark-mode .card {
|
||||
background: #2d3748;
|
||||
border-color: #4a5568;
|
||||
@@ -1070,3 +1215,76 @@ body.dark-mode hr {
|
||||
body.dark-mode .card-body {
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
QUILL RICH TEXT EDITOR STYLES
|
||||
============================================ */
|
||||
.ql-toolbar.ql-snow {
|
||||
border-radius: 8px 8px 0 0;
|
||||
background: #f8f9fa;
|
||||
border-color: #ced4da;
|
||||
}
|
||||
|
||||
.ql-container.ql-snow {
|
||||
border-radius: 0 0 8px 8px;
|
||||
border-color: #ced4da;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.ql-editor {
|
||||
min-height: 150px;
|
||||
max-height: 350px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ql-editor::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.ql-editor::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ql-editor::-webkit-scrollbar-thumb {
|
||||
background: #667eea;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ql-editor::-webkit-scrollbar-thumb:hover {
|
||||
background: #5a6fd6;
|
||||
}
|
||||
|
||||
/* Larger editor for blog posts and custom pages */
|
||||
.modal-xl .ql-editor {
|
||||
min-height: 300px;
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
/* Dark mode support for Quill */
|
||||
body.dark-mode .ql-toolbar.ql-snow {
|
||||
background: #374151;
|
||||
border-color: #4a5568;
|
||||
}
|
||||
|
||||
body.dark-mode .ql-toolbar.ql-snow .ql-stroke {
|
||||
stroke: #f0f0f0;
|
||||
}
|
||||
|
||||
body.dark-mode .ql-toolbar.ql-snow .ql-fill {
|
||||
fill: #f0f0f0;
|
||||
}
|
||||
|
||||
body.dark-mode .ql-toolbar.ql-snow .ql-picker {
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
body.dark-mode .ql-container.ql-snow {
|
||||
background: #2d3748;
|
||||
border-color: #4a5568;
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
body.dark-mode .ql-editor.ql-blank::before {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
809
website/admin/css/media-library.css
Normal file
809
website/admin/css/media-library.css
Normal file
@@ -0,0 +1,809 @@
|
||||
/**
|
||||
* Modern Media Library Styles
|
||||
* Clean, professional design with smooth animations
|
||||
*/
|
||||
|
||||
/* Modal Styles */
|
||||
.media-library-modal .modal-content {
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.media-library-modal .modal-xl {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.media-library-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 1rem 1.5rem;
|
||||
border: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.media-library-header .header-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.media-library-header .header-left .back-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.35rem 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
color: white;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.media-library-header .header-left .back-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
.media-library-header .title-breadcrumb {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.media-library-header .modal-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.media-library-header .modal-title i {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.media-library-header .breadcrumb-nav .breadcrumb {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.media-library-header .breadcrumb-item a {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.media-library-header .breadcrumb-item a:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.media-library-header .breadcrumb-item.active {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.media-library-header .breadcrumb-item + .breadcrumb-item::before {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.media-library-header .header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.media-library-header .view-toggle .btn {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.media-library-header .view-toggle .btn:hover,
|
||||
.media-library-header .view-toggle .btn.active {
|
||||
color: white;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.media-library-header .btn-close {
|
||||
filter: brightness(0) invert(1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.media-library-header .btn-close:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Body */
|
||||
.media-library-body {
|
||||
padding: 0;
|
||||
background: #f8fafc;
|
||||
min-height: 500px;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Toolbar */
|
||||
.media-library-toolbar {
|
||||
background: white;
|
||||
padding: 1rem 1.5rem;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.media-library-toolbar .toolbar-left,
|
||||
.media-library-toolbar .toolbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.media-library-toolbar .divider {
|
||||
width: 1px;
|
||||
height: 24px;
|
||||
background: #e2e8f0;
|
||||
}
|
||||
|
||||
.media-library-toolbar .search-box {
|
||||
position: relative;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.media-library-toolbar .search-box i {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.media-library-toolbar .search-box input {
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem 0.5rem 2.25rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
font-size: 0.875rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.media-library-toolbar .search-box input:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
.media-library-toolbar .selected-count {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
padding: 0.375rem 0.875rem;
|
||||
border-radius: 20px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
/* Upload Drop Zone */
|
||||
.upload-drop-zone {
|
||||
margin: 1rem 1.5rem;
|
||||
padding: 2rem;
|
||||
border: 2px dashed #cbd5e1;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.upload-drop-zone:hover,
|
||||
.upload-drop-zone.dragover {
|
||||
border-color: #667eea;
|
||||
background: #f0f4ff;
|
||||
}
|
||||
|
||||
.upload-drop-zone .drop-zone-content i {
|
||||
font-size: 3rem;
|
||||
color: #667eea;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.upload-drop-zone .drop-zone-content p {
|
||||
margin: 0.5rem 0;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.upload-drop-zone .browse-link {
|
||||
color: #667eea;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.upload-drop-zone .drop-zone-content small {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
/* Upload Progress */
|
||||
.upload-progress {
|
||||
margin: 1rem 1.5rem;
|
||||
padding: 1rem;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.upload-progress .progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.upload-progress .progress-percent {
|
||||
font-weight: 600;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.upload-progress .progress {
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.upload-progress .progress-bar {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
/* Media Content */
|
||||
.media-content {
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.media-content.grid-view {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.media-content.list-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Media Item - Grid View */
|
||||
.media-content.grid-view .media-item {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: 2px solid transparent;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.media-content.grid-view .media-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.media-content.grid-view .media-item.selected {
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
|
||||
.media-content.grid-view .media-item .item-checkbox {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.media-content.grid-view .media-item .item-checkbox input {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
accent-color: #667eea;
|
||||
}
|
||||
|
||||
.media-content.grid-view .folder-item {
|
||||
position: relative;
|
||||
height: 160px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
|
||||
}
|
||||
|
||||
.media-content.grid-view .folder-item .item-icon {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.media-content.grid-view .folder-item .item-icon i {
|
||||
font-size: 3rem;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.media-content.grid-view .folder-item .item-info {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.media-content.grid-view .folder-item .item-name {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #334155;
|
||||
display: block;
|
||||
max-width: 130px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.media-content.grid-view .folder-item .item-meta {
|
||||
font-size: 0.75rem;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.media-content.grid-view .file-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.media-content.grid-view .file-item .item-preview {
|
||||
height: 120px;
|
||||
background: #f1f5f9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.media-content.grid-view .file-item .item-preview img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.video-preview-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.video-preview-placeholder i {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
.video-preview-placeholder span {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.media-content.grid-view .file-item .item-info {
|
||||
padding: 0.75rem;
|
||||
background: white;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
}
|
||||
|
||||
.media-content.grid-view .file-item .item-name {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
color: #334155;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.media-content.grid-view .file-item .item-meta {
|
||||
font-size: 0.7rem;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.media-content.grid-view .media-item .item-actions {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.media-content.grid-view .media-item:hover .item-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Media Item - List View */
|
||||
.media-content.list-view .media-item {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.media-content.list-view .media-item:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.media-content.list-view .media-item.selected {
|
||||
border-color: #667eea;
|
||||
background: #f0f4ff;
|
||||
}
|
||||
|
||||
.media-content.list-view .media-item .item-checkbox input {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
accent-color: #667eea;
|
||||
}
|
||||
|
||||
.media-content.list-view .folder-item .item-icon i {
|
||||
font-size: 1.75rem;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.media-content.list-view .file-item .item-preview {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background: #f1f5f9;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.media-content.list-view .file-item .item-preview img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.media-content.list-view .media-item .item-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.media-content.list-view .media-item .item-name {
|
||||
font-weight: 500;
|
||||
color: #334155;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.media-content.list-view .media-item .item-meta {
|
||||
font-size: 0.8rem;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.media-content.list-view .media-item .item-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.media-content.list-view .media-item:hover .item-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Action Buttons */
|
||||
.btn-action {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: #64748b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.btn-action:hover {
|
||||
background: white;
|
||||
color: #667eea;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.btn-action.btn-danger:hover {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.media-content .empty-state {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
color: #94a3b8;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.media-content .empty-state i {
|
||||
font-size: 4rem;
|
||||
margin-bottom: 1rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.media-content .empty-state h5 {
|
||||
color: #64748b;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* Drag Over States */
|
||||
.media-item.dragging {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.folder-item.drag-over {
|
||||
background: linear-gradient(135deg, #e0e7ff 0%, #c7d2fe 100%) !important;
|
||||
border: 2px dashed #667eea !important;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.media-library-footer {
|
||||
background: white;
|
||||
padding: 1rem 1.5rem;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.media-library-footer .footer-left,
|
||||
.media-library-footer .footer-right {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Image Preview Overlay */
|
||||
.image-preview-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
z-index: 10100;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.image-preview-overlay.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.image-preview-overlay .preview-close {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.image-preview-overlay .preview-close:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.image-preview-overlay img {
|
||||
max-width: 90%;
|
||||
max-height: 80%;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.image-preview-overlay .preview-info {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 25px;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
color: white;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.image-preview-overlay .preview-filename {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.image-preview-overlay .preview-size {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
/* Toast Notifications */
|
||||
.media-toast {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
padding: 1rem 1.5rem;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
z-index: 10200;
|
||||
transform: translateX(120%);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.media-toast.show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.media-toast.success {
|
||||
border-left: 4px solid #10b981;
|
||||
}
|
||||
|
||||
.media-toast.success i {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.media-toast.error {
|
||||
border-left: 4px solid #ef4444;
|
||||
}
|
||||
|
||||
.media-toast.error i {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.media-toast.info {
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
|
||||
.media-toast.info i {
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.media-toast i {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
/* Rename & Create Folder Modals */
|
||||
#renameModal .modal-content,
|
||||
#createFolderModal .modal-content {
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#renameModal .modal-header,
|
||||
#createFolderModal .modal-header {
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
#renameModal .modal-title,
|
||||
#createFolderModal .modal-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#renameModal .modal-title i,
|
||||
#createFolderModal .modal-title i {
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
#renameInput,
|
||||
#newFolderInput {
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
#renameInput:focus,
|
||||
#newFolderInput:focus {
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
/* Loading Spinner */
|
||||
.media-content .loading-spinner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4rem;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
/* Button Styles */
|
||||
.media-library-toolbar .btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.media-library-toolbar .btn-primary:hover {
|
||||
background: linear-gradient(135deg, #5a6fd6 0%, #6a4190 100%);
|
||||
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.media-library-footer .btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.media-library-footer .btn-primary:hover {
|
||||
background: linear-gradient(135deg, #5a6fd6 0%, #6a4190 100%);
|
||||
}
|
||||
|
||||
.media-library-footer .btn-primary:disabled {
|
||||
background: #94a3b8;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.media-library-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.media-library-toolbar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.media-library-toolbar .toolbar-left,
|
||||
.media-library-toolbar .toolbar-right {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.media-library-toolbar .search-box {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.media-content.grid-view {
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
}
|
||||
|
||||
.media-library-footer {
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
}
|
||||
940
website/admin/customers.html
Normal file
940
website/admin/customers.html
Normal file
@@ -0,0 +1,940 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Customer 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>
|
||||
.stats-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 1.25rem;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.stat-card i {
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.stat-card .stat-number {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.stat-card .stat-label {
|
||||
color: #666;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.stat-card.total i {
|
||||
color: #9c27b0;
|
||||
}
|
||||
.stat-card.verified i {
|
||||
color: #2196f3;
|
||||
}
|
||||
.stat-card.newsletter i {
|
||||
color: #e91e63;
|
||||
}
|
||||
.stat-card.active i {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 1rem 1.25rem;
|
||||
margin-bottom: 1rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-bar .search-box {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content-card table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.content-card th {
|
||||
background: #f8f9fa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.content-card td {
|
||||
vertical-align: middle;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.customer-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #e91e63 0%, #9c27b0 100%);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.badge-newsletter {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.pagination-bar {
|
||||
padding: 1rem;
|
||||
background: #f8f9fa;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 3rem 2rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
font-size: 3rem;
|
||||
color: #e0e0e0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.btn-export {
|
||||
background: linear-gradient(135deg, #e91e63 0%, #9c27b0 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-export:hover {
|
||||
color: white;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.stats-row {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.stats-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</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"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio"><i class="bi bi-easel"></i> Portfolio</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/customers" class="active"
|
||||
><i class="bi bi-person-hearts"></i> Customers</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content">
|
||||
<!-- Top Bar -->
|
||||
<div class="top-bar">
|
||||
<div>
|
||||
<h3>Customer Management</h3>
|
||||
<p class="mb-0 text-muted">View and manage registered customers</p>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-export me-2" onclick="exportNewsletterList()">
|
||||
<i class="bi bi-download"></i> Export Newsletter
|
||||
</button>
|
||||
<button class="btn-logout" id="btnLogout">
|
||||
<i class="bi bi-box-arrow-right"></i> Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="stats-row">
|
||||
<div class="stat-card total">
|
||||
<i class="bi bi-people"></i>
|
||||
<div class="stat-number" id="statTotal">-</div>
|
||||
<div class="stat-label">Total Customers</div>
|
||||
</div>
|
||||
<div class="stat-card verified">
|
||||
<i class="bi bi-patch-check"></i>
|
||||
<div class="stat-number" id="statVerified">-</div>
|
||||
<div class="stat-label">Verified</div>
|
||||
</div>
|
||||
<div class="stat-card newsletter">
|
||||
<i class="bi bi-envelope-heart"></i>
|
||||
<div class="stat-number" id="statNewsletter">-</div>
|
||||
<div class="stat-label">Newsletter</div>
|
||||
</div>
|
||||
<div class="stat-card active">
|
||||
<i class="bi bi-person-check"></i>
|
||||
<div class="stat-number" id="statActive">-</div>
|
||||
<div class="stat-label">Active</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Bar -->
|
||||
<div class="filter-bar">
|
||||
<div class="search-box">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-search"></i></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="searchInput"
|
||||
placeholder="Search by name or email..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<select class="form-select" id="statusFilter" style="width: auto">
|
||||
<option value="all">All Status</option>
|
||||
<option value="verified">Verified</option>
|
||||
<option value="unverified">Unverified</option>
|
||||
</select>
|
||||
<select class="form-select" id="newsletterFilter" style="width: auto">
|
||||
<option value="all">All Customers</option>
|
||||
<option value="subscribed">Newsletter Subscribed</option>
|
||||
<option value="unsubscribed">Not Subscribed</option>
|
||||
</select>
|
||||
<button class="btn btn-outline-secondary" onclick="loadCustomers()">
|
||||
<i class="bi bi-funnel"></i> Filter
|
||||
</button>
|
||||
<span class="text-muted ms-auto" id="resultCount">Loading...</span>
|
||||
</div>
|
||||
|
||||
<!-- Customers Table -->
|
||||
<div class="content-card">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 50px"></th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th class="text-center">Verified</th>
|
||||
<th class="text-center">Newsletter</th>
|
||||
<th class="text-center">Cart</th>
|
||||
<th class="text-center">Wishlist</th>
|
||||
<th class="text-center">Logins</th>
|
||||
<th>Joined</th>
|
||||
<th class="text-center">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="customersTableBody">
|
||||
<tr>
|
||||
<td colspan="10" class="text-center py-5">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="pagination-bar">
|
||||
<span class="text-muted" id="paginationInfo">Showing 0 of 0</span>
|
||||
<nav>
|
||||
<ul
|
||||
class="pagination pagination-sm mb-0"
|
||||
id="paginationControls"
|
||||
></ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Customer Detail Modal -->
|
||||
<div class="modal fade" id="customerModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Customer Details</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4 text-center">
|
||||
<div
|
||||
class="customer-avatar mx-auto mb-2"
|
||||
style="width: 80px; height: 80px; font-size: 2rem"
|
||||
id="modalAvatar"
|
||||
>
|
||||
?
|
||||
</div>
|
||||
<h5 id="modalName">-</h5>
|
||||
<p class="text-muted" id="modalEmail">-</p>
|
||||
<div class="d-flex justify-content-center gap-3 mb-3">
|
||||
<div>
|
||||
<div class="fw-bold" id="modalLogins">0</div>
|
||||
<small class="text-muted">Logins</small>
|
||||
</div>
|
||||
<div>
|
||||
<div id="modalNewsletter">-</div>
|
||||
<small class="text-muted">Newsletter</small>
|
||||
</div>
|
||||
<div>
|
||||
<div id="modalStatus">-</div>
|
||||
<small class="text-muted">Status</small>
|
||||
</div>
|
||||
</div>
|
||||
<p class="small text-muted mb-1">
|
||||
Member Since: <span id="modalJoined">-</span>
|
||||
</p>
|
||||
<p class="small text-muted">
|
||||
Last Login: <span id="modalLastLogin">-</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<ul class="nav nav-tabs mb-3" id="customerTabs">
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link active"
|
||||
data-bs-toggle="tab"
|
||||
href="#tabCart"
|
||||
>Cart (<span id="cartCount">0</span>)</a
|
||||
>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" href="#tabWishlist"
|
||||
>Wishlist (<span id="wishlistCount">0</span>)</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tabCart">
|
||||
<div
|
||||
id="cartItems"
|
||||
class="list-group list-group-flush"
|
||||
style="max-height: 300px; overflow-y: auto"
|
||||
>
|
||||
<p class="text-muted text-center py-3">
|
||||
No items in cart
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="tabWishlist">
|
||||
<div
|
||||
id="wishlistItems"
|
||||
class="list-group list-group-flush"
|
||||
style="max-height: 300px; overflow-y: auto"
|
||||
>
|
||||
<p class="text-muted text-center py-3">
|
||||
No items in wishlist
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-warning"
|
||||
id="modalToggleBtn"
|
||||
onclick="toggleCustomerStatus()"
|
||||
>
|
||||
Deactivate
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
// State
|
||||
let currentPage = 1;
|
||||
let totalPages = 1;
|
||||
let currentCustomerId = null;
|
||||
let currentCustomerActive = true;
|
||||
|
||||
// Initialize
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
loadStats();
|
||||
loadCustomers();
|
||||
|
||||
// Search on Enter
|
||||
document
|
||||
.getElementById("searchInput")
|
||||
.addEventListener("keyup", (e) => {
|
||||
if (e.key === "Enter") loadCustomers();
|
||||
});
|
||||
|
||||
// Filter change
|
||||
document
|
||||
.getElementById("newsletterFilter")
|
||||
.addEventListener("change", loadCustomers);
|
||||
document
|
||||
.getElementById("statusFilter")
|
||||
.addEventListener("change", loadCustomers);
|
||||
|
||||
// Logout button
|
||||
document.getElementById("btnLogout").addEventListener("click", logout);
|
||||
});
|
||||
|
||||
// Logout function
|
||||
async function logout() {
|
||||
try {
|
||||
await fetch("/api/admin/logout", { method: "POST" });
|
||||
window.location.href = "/admin/login";
|
||||
} catch (error) {
|
||||
console.error("Logout failed:", error);
|
||||
window.location.href = "/admin/login";
|
||||
}
|
||||
}
|
||||
|
||||
// Load statistics
|
||||
async function loadStats() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/customers/stats/overview");
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
document.getElementById("statTotal").textContent = data.stats.total;
|
||||
document.getElementById("statVerified").textContent =
|
||||
data.stats.verified;
|
||||
document.getElementById("statNewsletter").textContent =
|
||||
data.stats.newsletterSubscribed;
|
||||
document.getElementById("statActive").textContent =
|
||||
data.stats.active;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load stats:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Load customers
|
||||
async function loadCustomers() {
|
||||
const search = document.getElementById("searchInput").value;
|
||||
const newsletter = document.getElementById("newsletterFilter").value;
|
||||
const status = document.getElementById("statusFilter").value;
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: currentPage,
|
||||
limit: 20,
|
||||
newsletter,
|
||||
status,
|
||||
search,
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/customers?${params}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
renderCustomers(data.customers);
|
||||
updatePagination(data.pagination);
|
||||
document.getElementById("resultCount").textContent =
|
||||
`${data.pagination.total} customers`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load customers:", error);
|
||||
document.getElementById("customersTableBody").innerHTML = `
|
||||
<tr>
|
||||
<td colspan="10">
|
||||
<div class="empty-state">
|
||||
<i class="bi bi-exclamation-triangle"></i>
|
||||
<h5>Failed to load customers</h5>
|
||||
<p>Please try refreshing the page.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Render customers table
|
||||
function renderCustomers(customers) {
|
||||
const tbody = document.getElementById("customersTableBody");
|
||||
|
||||
if (customers.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="10">
|
||||
<div class="empty-state">
|
||||
<i class="bi bi-people"></i>
|
||||
<h5>No customers found</h5>
|
||||
<p>No customers match your search criteria.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = customers
|
||||
.map(
|
||||
(customer) => `
|
||||
<tr>
|
||||
<td>
|
||||
<div class="customer-avatar">
|
||||
${customer.first_name.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<strong>${escapeHtml(customer.first_name)} ${escapeHtml(
|
||||
customer.last_name,
|
||||
)}</strong>
|
||||
</td>
|
||||
<td>
|
||||
<a href="mailto:${escapeHtml(customer.email)}">${escapeHtml(
|
||||
customer.email,
|
||||
)}</a>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
${
|
||||
customer.email_verified
|
||||
? '<span class="badge bg-success"><i class="bi bi-check-circle"></i></span>'
|
||||
: '<span class="badge bg-warning text-dark"><i class="bi bi-clock"></i></span>'
|
||||
}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
${
|
||||
customer.newsletter_subscribed
|
||||
? '<span class="badge bg-success badge-newsletter"><i class="bi bi-check"></i></span>'
|
||||
: '<span class="badge bg-secondary badge-newsletter"><i class="bi bi-x"></i></span>'
|
||||
}
|
||||
</td>
|
||||
<td class="text-center"><span class="badge bg-info">${
|
||||
customer.cart_count || 0
|
||||
}</span></td>
|
||||
<td class="text-center"><span class="badge bg-pink">${
|
||||
customer.wishlist_count || 0
|
||||
}</span></td>
|
||||
<td class="text-center">${customer.login_count || 0}</td>
|
||||
<td>${formatDate(customer.created_at)}</td>
|
||||
<td class="text-center">
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="viewCustomer('${
|
||||
customer.id
|
||||
}')">
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
// Update pagination
|
||||
function updatePagination(pagination) {
|
||||
totalPages = pagination.totalPages || 1;
|
||||
const start =
|
||||
pagination.total > 0
|
||||
? (pagination.page - 1) * pagination.limit + 1
|
||||
: 0;
|
||||
const end = Math.min(
|
||||
pagination.page * pagination.limit,
|
||||
pagination.total,
|
||||
);
|
||||
|
||||
document.getElementById("paginationInfo").textContent =
|
||||
`Showing ${start}-${end} of ${pagination.total}`;
|
||||
|
||||
const controls = document.getElementById("paginationControls");
|
||||
let html = "";
|
||||
|
||||
if (totalPages > 1) {
|
||||
html += `
|
||||
<li class="page-item ${pagination.page === 1 ? "disabled" : ""}">
|
||||
<a class="page-link" href="#" onclick="goToPage(${
|
||||
pagination.page - 1
|
||||
}); return false;">
|
||||
<i class="bi bi-chevron-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
if (
|
||||
i === 1 ||
|
||||
i === totalPages ||
|
||||
(i >= pagination.page - 1 && i <= pagination.page + 1)
|
||||
) {
|
||||
html += `
|
||||
<li class="page-item ${i === pagination.page ? "active" : ""}">
|
||||
<a class="page-link" href="#" onclick="goToPage(${i}); return false;">${i}</a>
|
||||
</li>
|
||||
`;
|
||||
} else if (i === pagination.page - 2 || i === pagination.page + 2) {
|
||||
html += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
|
||||
}
|
||||
}
|
||||
|
||||
html += `
|
||||
<li class="page-item ${
|
||||
pagination.page === totalPages ? "disabled" : ""
|
||||
}">
|
||||
<a class="page-link" href="#" onclick="goToPage(${
|
||||
pagination.page + 1
|
||||
}); return false;">
|
||||
<i class="bi bi-chevron-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
|
||||
controls.innerHTML = html;
|
||||
}
|
||||
|
||||
// Go to page
|
||||
function goToPage(page) {
|
||||
if (page < 1 || page > totalPages) return;
|
||||
currentPage = page;
|
||||
loadCustomers();
|
||||
}
|
||||
|
||||
// View customer details
|
||||
async function viewCustomer(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/customers/${id}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
const customer = data.customer;
|
||||
currentCustomerId = customer.id;
|
||||
currentCustomerActive = customer.is_active;
|
||||
|
||||
document.getElementById("modalAvatar").textContent =
|
||||
customer.first_name.charAt(0).toUpperCase();
|
||||
document.getElementById("modalName").textContent =
|
||||
`${customer.first_name} ${customer.last_name}`;
|
||||
document.getElementById("modalEmail").textContent = customer.email;
|
||||
document.getElementById("modalLogins").textContent =
|
||||
customer.login_count || 0;
|
||||
document.getElementById("modalNewsletter").innerHTML =
|
||||
customer.newsletter_subscribed
|
||||
? '<span class="text-success"><i class="bi bi-check-circle"></i></span>'
|
||||
: '<span class="text-muted"><i class="bi bi-x-circle"></i></span>';
|
||||
document.getElementById("modalStatus").innerHTML =
|
||||
customer.is_active
|
||||
? '<span class="text-success">Active</span>'
|
||||
: '<span class="text-danger">Inactive</span>';
|
||||
document.getElementById("modalJoined").textContent = formatDate(
|
||||
customer.created_at,
|
||||
true,
|
||||
);
|
||||
document.getElementById("modalLastLogin").textContent =
|
||||
customer.last_login
|
||||
? formatDate(customer.last_login, true)
|
||||
: "Never";
|
||||
|
||||
document.getElementById("modalToggleBtn").textContent =
|
||||
customer.is_active ? "Deactivate" : "Activate";
|
||||
document.getElementById("modalToggleBtn").className =
|
||||
customer.is_active ? "btn btn-warning" : "btn btn-success";
|
||||
|
||||
// Load cart and wishlist
|
||||
loadCustomerCart(id);
|
||||
loadCustomerWishlist(id);
|
||||
|
||||
new bootstrap.Modal(
|
||||
document.getElementById("customerModal"),
|
||||
).show();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load customer:", error);
|
||||
alert("Failed to load customer details");
|
||||
}
|
||||
}
|
||||
|
||||
// Load customer cart
|
||||
async function loadCustomerCart(customerId) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/admin/customers/${customerId}/cart`,
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
const container = document.getElementById("cartItems");
|
||||
const countEl = document.getElementById("cartCount");
|
||||
|
||||
if (data.success && data.items && data.items.length > 0) {
|
||||
countEl.textContent = data.items.length;
|
||||
container.innerHTML = data.items
|
||||
.map(
|
||||
(item) => `
|
||||
<div class="list-group-item d-flex align-items-center">
|
||||
<img src="${
|
||||
item.image || "/assets/images/products/placeholder.jpg"
|
||||
}"
|
||||
alt="${escapeHtml(item.name)}"
|
||||
style="width: 50px; height: 50px; object-fit: cover; border-radius: 8px;"
|
||||
class="me-3">
|
||||
<div class="flex-grow-1">
|
||||
<div class="fw-semibold">${escapeHtml(item.name)}</div>
|
||||
<small class="text-muted">Qty: ${item.quantity}</small>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="fw-bold">$${(item.price * item.quantity).toFixed(
|
||||
2,
|
||||
)}</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
} else {
|
||||
countEl.textContent = "0";
|
||||
container.innerHTML =
|
||||
'<p class="text-muted text-center py-3">No items in cart</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load cart:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Load customer wishlist
|
||||
async function loadCustomerWishlist(customerId) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/admin/customers/${customerId}/wishlist`,
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
const container = document.getElementById("wishlistItems");
|
||||
const countEl = document.getElementById("wishlistCount");
|
||||
|
||||
if (data.success && data.items && data.items.length > 0) {
|
||||
countEl.textContent = data.items.length;
|
||||
container.innerHTML = data.items
|
||||
.map(
|
||||
(item) => `
|
||||
<div class="list-group-item d-flex align-items-center">
|
||||
<img src="${
|
||||
item.image || "/assets/images/products/placeholder.jpg"
|
||||
}"
|
||||
alt="${escapeHtml(item.name)}"
|
||||
style="width: 50px; height: 50px; object-fit: cover; border-radius: 8px;"
|
||||
class="me-3">
|
||||
<div class="flex-grow-1">
|
||||
<div class="fw-semibold">${escapeHtml(item.name)}</div>
|
||||
<small class="text-muted">Added: ${formatDate(
|
||||
item.added_at,
|
||||
)}</small>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="fw-bold">$${parseFloat(item.price).toFixed(
|
||||
2,
|
||||
)}</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
} else {
|
||||
countEl.textContent = "0";
|
||||
container.innerHTML =
|
||||
'<p class="text-muted text-center py-3">No items in wishlist</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load wishlist:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle customer status
|
||||
async function toggleCustomerStatus() {
|
||||
if (!currentCustomerId) return;
|
||||
|
||||
const newStatus = !currentCustomerActive;
|
||||
const action = newStatus ? "activate" : "deactivate";
|
||||
|
||||
if (!confirm(`Are you sure you want to ${action} this customer?`))
|
||||
return;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/admin/customers/${currentCustomerId}/status`,
|
||||
{
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ is_active: newStatus }),
|
||||
},
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
bootstrap.Modal.getInstance(
|
||||
document.getElementById("customerModal"),
|
||||
).hide();
|
||||
loadCustomers();
|
||||
loadStats();
|
||||
} else {
|
||||
alert(data.message || "Failed to update status");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to update status:", error);
|
||||
alert("Failed to update customer status");
|
||||
}
|
||||
}
|
||||
|
||||
// Export newsletter list
|
||||
async function exportNewsletterList() {
|
||||
try {
|
||||
const response = await fetch(
|
||||
"/api/admin/customers/export/newsletter",
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
const headers = ["First Name", "Last Name", "Email"];
|
||||
const rows = data.customers.map((c) => [
|
||||
c.first_name,
|
||||
c.last_name,
|
||||
c.email,
|
||||
]);
|
||||
|
||||
let csv = headers.join(",") + "\n";
|
||||
csv += rows.map((r) => r.map((v) => `"${v}"`).join(",")).join("\n");
|
||||
|
||||
const blob = new Blob([csv], { type: "text/csv" });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `newsletter-subscribers-${
|
||||
new Date().toISOString().split("T")[0]
|
||||
}.csv`;
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
alert(`Exported ${data.count} newsletter subscribers`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to export:", error);
|
||||
alert("Failed to export newsletter list");
|
||||
}
|
||||
}
|
||||
|
||||
// Format date
|
||||
function formatDate(dateStr, full = false) {
|
||||
if (!dateStr) return "-";
|
||||
const date = new Date(dateStr);
|
||||
if (full) {
|
||||
return date.toLocaleString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
return date.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
// Escape HTML
|
||||
function escapeHtml(str) {
|
||||
if (!str) return "";
|
||||
return str
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -13,6 +13,7 @@
|
||||
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>
|
||||
:root {
|
||||
--primary-gradient: #202023;
|
||||
@@ -27,7 +28,8 @@
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
overflow-x: hidden;
|
||||
@@ -406,7 +408,9 @@
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages"><i class="bi bi-file-text"></i> Pages</a>
|
||||
<a href="/admin/pages"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library"
|
||||
@@ -422,6 +426,11 @@
|
||||
<li>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/customers"
|
||||
><i class="bi bi-person-hearts"></i> Customers</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -13,11 +13,13 @@
|
||||
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" />
|
||||
<link rel="stylesheet" href="/admin/css/media-library.css" />
|
||||
<!-- Quill Rich Text Editor -->
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/quill@1.3.6/dist/quill.snow.css"
|
||||
href="https://cdn.quilljs.com/1.3.7/quill.snow.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="stylesheet" href="/admin/css/admin-style.css" />
|
||||
<style>
|
||||
.section-builder {
|
||||
background: white;
|
||||
@@ -33,11 +35,6 @@
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.section-builder.disabled {
|
||||
opacity: 0.6;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -59,10 +56,60 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Slide Management */
|
||||
.slides-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.slide-card {
|
||||
background: #f8f9fa;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.slide-card:hover {
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
|
||||
}
|
||||
|
||||
.slide-card.active {
|
||||
border-color: #28a745;
|
||||
background: #f8fff9;
|
||||
}
|
||||
|
||||
.slide-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.slide-number {
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.slide-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.slide-actions .btn {
|
||||
padding: 5px 10px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.image-preview {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
height: 200px;
|
||||
height: 150px;
|
||||
border: 2px dashed #ccc;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
@@ -83,30 +130,23 @@
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.alignment-selector {
|
||||
.add-slide-btn {
|
||||
border: 2px dashed #667eea;
|
||||
background: transparent;
|
||||
color: #667eea;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
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;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.alignment-btn:hover {
|
||||
border-color: #667eea;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.alignment-btn.active {
|
||||
border-color: #667eea;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
.add-slide-btn:hover {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@@ -118,34 +158,99 @@
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
/* Quill Editor Styling */
|
||||
.ql-container {
|
||||
min-height: 150px;
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
.drag-handle {
|
||||
cursor: grab;
|
||||
color: #adb5bd;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.ql-toolbar {
|
||||
.drag-handle:hover {
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
/* Featured Products Section */
|
||||
.count-selector {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.count-btn {
|
||||
padding: 8px 16px;
|
||||
border: 2px solid #e9ecef;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.count-btn:hover {
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.count-btn.active {
|
||||
border-color: #667eea;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Info box */
|
||||
.info-box {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
|
||||
border-left: 4px solid #2196f3;
|
||||
padding: 15px;
|
||||
border-radius: 0 8px 8px 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.info-box i {
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
/* Quill Editor Styles */
|
||||
.quill-container {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.quill-container .ql-toolbar {
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
background: #f8f9fa;
|
||||
border-color: #ced4da;
|
||||
}
|
||||
|
||||
.ql-editor {
|
||||
min-height: 150px;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
.quill-container .ql-container {
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
border-color: #ced4da;
|
||||
min-height: 120px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ql-editor.ql-blank::before {
|
||||
color: #adb5bd;
|
||||
font-style: italic;
|
||||
.quill-container .ql-editor {
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.quill-container .ql-editor.ql-blank::before {
|
||||
font-style: normal;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.slide-card .quill-container .ql-container {
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.slide-card .quill-container .ql-editor {
|
||||
min-height: 60px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<div class="sidebar-brand">Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard"
|
||||
@@ -161,9 +266,7 @@
|
||||
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
<a href="/admin/portfolio"><i class="bi bi-easel"></i> Portfolio</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
@@ -187,6 +290,11 @@
|
||||
<li>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/customers"
|
||||
><i class="bi bi-person-hearts"></i> Customers</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -197,7 +305,7 @@
|
||||
<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">
|
||||
<a href="/home" target="_blank" class="btn btn-info me-2">
|
||||
<i class="bi bi-eye"></i> Preview
|
||||
</a>
|
||||
<button class="btn-logout" onclick="logout()">
|
||||
@@ -207,234 +315,122 @@
|
||||
</div>
|
||||
|
||||
<div id="sectionsContainer">
|
||||
<!-- Hero Section -->
|
||||
<div class="section-builder" id="heroSection">
|
||||
<!-- Hero Slider Section -->
|
||||
<div class="section-builder" id="heroSliderSection">
|
||||
<div class="section-header">
|
||||
<h5><i class="bi bi-stars"></i> Hero Section</h5>
|
||||
<h5><i class="bi bi-collection-play"></i> Hero Slider</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>
|
||||
<span class="badge bg-primary" id="slideCount">0 slides</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<p class="mb-2">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
<strong>Hero Slider:</strong> Create multiple slides with
|
||||
background images, titles, descriptions, and call-to-action
|
||||
buttons. Slides will auto-rotate every 10 seconds on the frontend.
|
||||
</p>
|
||||
<p
|
||||
class="mb-0"
|
||||
style="
|
||||
background: #fff3cd;
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #ffc107;
|
||||
"
|
||||
>
|
||||
<i class="bi bi-image me-2" style="color: #856404"></i>
|
||||
<strong style="color: #856404">Recommended Image Size:</strong>
|
||||
<code
|
||||
style="
|
||||
background: #ffeeba;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
"
|
||||
>1920 x 600 pixels</code
|
||||
>
|
||||
(width x height). Use landscape images for best results. The image
|
||||
will cover the entire slider area as a background.
|
||||
</p>
|
||||
</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 class="slides-container" id="slidesContainer">
|
||||
<!-- Slides will be rendered here -->
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<div
|
||||
id="heroDescription"
|
||||
style="background: white; min-height: 150px"
|
||||
></div>
|
||||
</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="hidden" id="heroBackgroundUrl" />
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary w-100"
|
||||
onclick="openMediaLibrary('hero', 'background')"
|
||||
>
|
||||
<i class="bi bi-folder2-open"></i> Choose from Media Library
|
||||
</button>
|
||||
<div class="image-preview empty" id="heroPreview">
|
||||
<i class="bi bi-image" style="font-size: 3rem"></i>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-danger mt-2"
|
||||
onclick="clearMedia('hero', 'background')"
|
||||
id="heroBackgroundClear"
|
||||
style="display: none"
|
||||
>
|
||||
<i class="bi bi-x-circle"></i> Clear Background
|
||||
</button>
|
||||
</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>
|
||||
<button
|
||||
type="button"
|
||||
class="add-slide-btn w-100 mt-3"
|
||||
onclick="addNewSlide()"
|
||||
>
|
||||
<i class="bi bi-plus-circle"></i> Add New Slide
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Promotion Section -->
|
||||
<div class="section-builder" id="promotionSection">
|
||||
<!-- Featured Products Section -->
|
||||
<div class="section-builder" id="featuredProductsSection">
|
||||
<div class="section-header">
|
||||
<h5><i class="bi bi-gift"></i> Promotion Section</h5>
|
||||
<h5><i class="bi bi-star"></i> Featured Products</h5>
|
||||
<div class="section-controls">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="promotionEnabled"
|
||||
id="featuredEnabled"
|
||||
checked
|
||||
onchange="toggleSection('promotion')"
|
||||
/>
|
||||
<label class="form-check-label" for="promotionEnabled"
|
||||
>Enabled</label
|
||||
<label class="form-check-label" for="featuredEnabled"
|
||||
>Show on Homepage</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<p class="mb-0">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
Products marked as "Featured" in the Products section will appear
|
||||
here automatically.
|
||||
</p>
|
||||
</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>
|
||||
<div
|
||||
id="promotionDescription"
|
||||
style="background: white; min-height: 150px"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Section Image</label>
|
||||
<input type="hidden" id="promotionImageUrl" />
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary w-100"
|
||||
onclick="openMediaLibrary('promotion', 'image')"
|
||||
>
|
||||
<i class="bi bi-folder2-open"></i> Choose from Media Library
|
||||
</button>
|
||||
<div class="image-preview empty" id="promotionPreview">
|
||||
<i class="bi bi-image" style="font-size: 3rem"></i>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-danger mt-2"
|
||||
onclick="clearMedia('promotion', 'image')"
|
||||
id="promotionImageClear"
|
||||
style="display: none"
|
||||
>
|
||||
<i class="bi bi-x-circle"></i> Clear Image
|
||||
</button>
|
||||
</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>
|
||||
<label class="form-label">Section Title</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="featuredTitle"
|
||||
value="Featured Products"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Text Alignment</label>
|
||||
<div class="alignment-selector">
|
||||
<label class="form-label">Number of Products to Display</label>
|
||||
<div class="count-selector">
|
||||
<button
|
||||
class="alignment-btn active"
|
||||
onclick="setTextAlignment('promotion', 'left')"
|
||||
class="count-btn"
|
||||
data-count="4"
|
||||
onclick="setFeaturedCount(4)"
|
||||
>
|
||||
<i class="bi bi-text-left"></i> Left
|
||||
4
|
||||
</button>
|
||||
<button
|
||||
class="alignment-btn"
|
||||
onclick="setTextAlignment('promotion', 'center')"
|
||||
class="count-btn active"
|
||||
data-count="8"
|
||||
onclick="setFeaturedCount(8)"
|
||||
>
|
||||
<i class="bi bi-text-center"></i> Center
|
||||
8
|
||||
</button>
|
||||
<button
|
||||
class="alignment-btn"
|
||||
onclick="setTextAlignment('promotion', 'right')"
|
||||
class="count-btn"
|
||||
data-count="12"
|
||||
onclick="setFeaturedCount(12)"
|
||||
>
|
||||
<i class="bi bi-text-right"></i> Right
|
||||
12
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -442,21 +438,81 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Portfolio Showcase Section -->
|
||||
<div class="section-builder" id="portfolioSection">
|
||||
<!-- Get Inspired / Blog Section -->
|
||||
<div class="section-builder" id="blogSection">
|
||||
<div class="section-header">
|
||||
<h5><i class="bi bi-easel"></i> Portfolio Showcase</h5>
|
||||
<h5><i class="bi bi-lightbulb"></i> Get Inspired (Blog Posts)</h5>
|
||||
<div class="section-controls">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="portfolioEnabled"
|
||||
id="blogEnabled"
|
||||
checked
|
||||
onchange="toggleSection('portfolio')"
|
||||
/>
|
||||
<label class="form-check-label" for="portfolioEnabled"
|
||||
>Enabled</label
|
||||
<label class="form-check-label" for="blogEnabled"
|
||||
>Show on Homepage</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<p class="mb-0">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
Latest blog posts will be displayed automatically from the Blog
|
||||
section.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="section-content">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Section Title</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="blogTitle"
|
||||
value="Get Inspired"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Number of Posts to Display</label>
|
||||
<div class="count-selector">
|
||||
<button
|
||||
class="count-btn active"
|
||||
data-count="3"
|
||||
onclick="setBlogCount(3)"
|
||||
>
|
||||
3
|
||||
</button>
|
||||
<button
|
||||
class="count-btn"
|
||||
data-count="6"
|
||||
onclick="setBlogCount(6)"
|
||||
>
|
||||
6
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- About Preview Section -->
|
||||
<div class="section-builder" id="aboutSection">
|
||||
<div class="section-header">
|
||||
<h5><i class="bi bi-info-square"></i> About Preview</h5>
|
||||
<div class="section-controls">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="aboutEnabled"
|
||||
checked
|
||||
/>
|
||||
<label class="form-check-label" for="aboutEnabled"
|
||||
>Show on Homepage</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -464,33 +520,33 @@
|
||||
|
||||
<div class="section-content">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Section Title</label>
|
||||
<label class="form-label">About Title</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="portfolioTitle"
|
||||
placeholder="Our Work"
|
||||
id="aboutTitle"
|
||||
value="About Sky Art Shop"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<div
|
||||
id="portfolioDescription"
|
||||
style="background: white; min-height: 150px"
|
||||
></div>
|
||||
<label class="form-label">About Description</label>
|
||||
<div class="quill-container">
|
||||
<div id="aboutDescriptionEditor"></div>
|
||||
</div>
|
||||
</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"
|
||||
/>
|
||||
<label class="form-label">About Image</label>
|
||||
<input type="hidden" id="aboutImageUrl" />
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary w-100"
|
||||
onclick="openMediaLibrary('about')"
|
||||
>
|
||||
<i class="bi bi-folder2-open"></i> Choose from Media Library
|
||||
</button>
|
||||
<div class="image-preview empty" id="aboutPreview">
|
||||
<i class="bi bi-image" style="font-size: 2rem"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -505,8 +561,10 @@
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@1.3.6/dist/quill.js"></script>
|
||||
<script src="https://cdn.quilljs.com/1.3.7/quill.min.js"></script>
|
||||
<script src="/admin/js/auth.js"></script>
|
||||
<script src="/admin/js/admin-utils.js"></script>
|
||||
<script src="/admin/js/media-library.js"></script>
|
||||
<script src="/admin/js/homepage.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
512
website/admin/homepage.html.old
Normal file
512
website/admin/homepage.html.old
Normal file
@@ -0,0 +1,512 @@
|
||||
<!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 rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||
<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
|
||||
href="https://cdn.jsdelivr.net/npm/quill@1.3.6/dist/quill.snow.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<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);
|
||||
}
|
||||
|
||||
/* Quill Editor Styling */
|
||||
.ql-container {
|
||||
min-height: 150px;
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
}
|
||||
|
||||
.ql-toolbar {
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.ql-editor {
|
||||
min-height: 150px;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.ql-editor.ql-blank::before {
|
||||
color: #adb5bd;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage" class="active"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users"><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>
|
||||
<div
|
||||
id="heroDescription"
|
||||
style="background: white; min-height: 150px"
|
||||
></div>
|
||||
</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="hidden" id="heroBackgroundUrl" />
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary w-100"
|
||||
onclick="openMediaLibrary('hero', 'background')"
|
||||
>
|
||||
<i class="bi bi-folder2-open"></i> Choose from Media Library
|
||||
</button>
|
||||
<div class="image-preview empty" id="heroPreview">
|
||||
<i class="bi bi-image" style="font-size: 3rem"></i>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-danger mt-2"
|
||||
onclick="clearMedia('hero', 'background')"
|
||||
id="heroBackgroundClear"
|
||||
style="display: none"
|
||||
>
|
||||
<i class="bi bi-x-circle"></i> Clear Background
|
||||
</button>
|
||||
</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>
|
||||
<div
|
||||
id="promotionDescription"
|
||||
style="background: white; min-height: 150px"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Section Image</label>
|
||||
<input type="hidden" id="promotionImageUrl" />
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary w-100"
|
||||
onclick="openMediaLibrary('promotion', 'image')"
|
||||
>
|
||||
<i class="bi bi-folder2-open"></i> Choose from Media Library
|
||||
</button>
|
||||
<div class="image-preview empty" id="promotionPreview">
|
||||
<i class="bi bi-image" style="font-size: 3rem"></i>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-danger mt-2"
|
||||
onclick="clearMedia('promotion', 'image')"
|
||||
id="promotionImageClear"
|
||||
style="display: none"
|
||||
>
|
||||
<i class="bi bi-x-circle"></i> Clear Image
|
||||
</button>
|
||||
</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>
|
||||
<div
|
||||
id="portfolioDescription"
|
||||
style="background: white; min-height: 150px"
|
||||
></div>
|
||||
</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="https://cdn.jsdelivr.net/npm/quill@1.3.6/dist/quill.js"></script>
|
||||
<script src="/admin/js/auth.js"></script>
|
||||
<script src="/admin/js/homepage.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
329
website/admin/js/admin-utils.js
Normal file
329
website/admin/js/admin-utils.js
Normal file
@@ -0,0 +1,329 @@
|
||||
// =====================================================
|
||||
// Admin Utilities - Shared Functions for Admin Panel
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* Show a custom confirmation dialog instead of browser confirm()
|
||||
* @param {string} message - The confirmation message
|
||||
* @param {Function} onConfirm - Callback when confirmed
|
||||
* @param {Object} options - Optional configuration
|
||||
*/
|
||||
function showDeleteConfirm(message, onConfirm, options = {}) {
|
||||
const {
|
||||
title = "Confirm Delete",
|
||||
confirmText = "Delete",
|
||||
cancelText = "Cancel",
|
||||
type = "danger",
|
||||
} = options;
|
||||
|
||||
// Check if modal already exists, if not create it
|
||||
let modal = document.getElementById("adminConfirmModal");
|
||||
if (!modal) {
|
||||
modal = document.createElement("div");
|
||||
modal.id = "adminConfirmModal";
|
||||
modal.className = "admin-confirm-modal";
|
||||
modal.innerHTML = `
|
||||
<div class="admin-confirm-overlay"></div>
|
||||
<div class="admin-confirm-dialog">
|
||||
<div class="admin-confirm-header">
|
||||
<div class="admin-confirm-icon ${type}">
|
||||
<i class="bi bi-exclamation-triangle-fill"></i>
|
||||
</div>
|
||||
<h3 class="admin-confirm-title">${title}</h3>
|
||||
</div>
|
||||
<div class="admin-confirm-body">
|
||||
<p class="admin-confirm-message">${message}</p>
|
||||
</div>
|
||||
<div class="admin-confirm-footer">
|
||||
<button type="button" class="admin-confirm-btn cancel">${cancelText}</button>
|
||||
<button type="button" class="admin-confirm-btn confirm ${type}">${confirmText}</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Add styles if not already present
|
||||
if (!document.getElementById("adminConfirmStyles")) {
|
||||
const styles = document.createElement("style");
|
||||
styles.id = "adminConfirmStyles";
|
||||
styles.textContent = `
|
||||
.admin-confirm-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.admin-confirm-modal.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
.admin-confirm-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.admin-confirm-dialog {
|
||||
position: relative;
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
transform: scale(0.9) translateY(-20px);
|
||||
transition: transform 0.2s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
.admin-confirm-modal.show .admin-confirm-dialog {
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
.admin-confirm-header {
|
||||
padding: 24px 24px 16px;
|
||||
text-align: center;
|
||||
}
|
||||
.admin-confirm-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 16px;
|
||||
font-size: 28px;
|
||||
}
|
||||
.admin-confirm-icon.danger {
|
||||
background: #fee2e2;
|
||||
color: #dc2626;
|
||||
}
|
||||
.admin-confirm-icon.warning {
|
||||
background: #fef3c7;
|
||||
color: #d97706;
|
||||
}
|
||||
.admin-confirm-icon.info {
|
||||
background: #dbeafe;
|
||||
color: #2563eb;
|
||||
}
|
||||
.admin-confirm-title {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
.admin-confirm-body {
|
||||
padding: 0 24px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.admin-confirm-message {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.admin-confirm-footer {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 16px 24px 24px;
|
||||
justify-content: center;
|
||||
}
|
||||
.admin-confirm-btn {
|
||||
padding: 10px 24px;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border: none;
|
||||
}
|
||||
.admin-confirm-btn.cancel {
|
||||
background: #f3f4f6;
|
||||
color: #374151;
|
||||
}
|
||||
.admin-confirm-btn.cancel:hover {
|
||||
background: #e5e7eb;
|
||||
}
|
||||
.admin-confirm-btn.confirm {
|
||||
color: white;
|
||||
}
|
||||
.admin-confirm-btn.confirm.danger {
|
||||
background: #dc2626;
|
||||
}
|
||||
.admin-confirm-btn.confirm.danger:hover {
|
||||
background: #b91c1c;
|
||||
}
|
||||
.admin-confirm-btn.confirm.warning {
|
||||
background: #d97706;
|
||||
}
|
||||
.admin-confirm-btn.confirm.warning:hover {
|
||||
background: #b45309;
|
||||
}
|
||||
.admin-confirm-btn.confirm.info {
|
||||
background: #2563eb;
|
||||
}
|
||||
.admin-confirm-btn.confirm.info:hover {
|
||||
background: #1d4ed8;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(styles);
|
||||
}
|
||||
} else {
|
||||
// Update existing modal content
|
||||
modal.querySelector(
|
||||
".admin-confirm-icon"
|
||||
).className = `admin-confirm-icon ${type}`;
|
||||
modal.querySelector(".admin-confirm-title").textContent = title;
|
||||
modal.querySelector(".admin-confirm-message").textContent = message;
|
||||
modal.querySelector(".admin-confirm-btn.confirm").textContent = confirmText;
|
||||
modal.querySelector(
|
||||
".admin-confirm-btn.confirm"
|
||||
).className = `admin-confirm-btn confirm ${type}`;
|
||||
modal.querySelector(".admin-confirm-btn.cancel").textContent = cancelText;
|
||||
}
|
||||
|
||||
// Show modal
|
||||
requestAnimationFrame(() => {
|
||||
modal.classList.add("show");
|
||||
});
|
||||
|
||||
// Get buttons
|
||||
const confirmBtn = modal.querySelector(".admin-confirm-btn.confirm");
|
||||
const cancelBtn = modal.querySelector(".admin-confirm-btn.cancel");
|
||||
const overlay = modal.querySelector(".admin-confirm-overlay");
|
||||
|
||||
// Close function
|
||||
const closeModal = () => {
|
||||
modal.classList.remove("show");
|
||||
};
|
||||
|
||||
// Remove old listeners by cloning
|
||||
const newConfirmBtn = confirmBtn.cloneNode(true);
|
||||
const newCancelBtn = cancelBtn.cloneNode(true);
|
||||
confirmBtn.parentNode.replaceChild(newConfirmBtn, confirmBtn);
|
||||
cancelBtn.parentNode.replaceChild(newCancelBtn, cancelBtn);
|
||||
|
||||
// Add new listeners
|
||||
newConfirmBtn.addEventListener("click", () => {
|
||||
closeModal();
|
||||
onConfirm();
|
||||
});
|
||||
|
||||
newCancelBtn.addEventListener("click", closeModal);
|
||||
overlay.addEventListener("click", closeModal);
|
||||
|
||||
// Escape key to close
|
||||
const escHandler = (e) => {
|
||||
if (e.key === "Escape") {
|
||||
closeModal();
|
||||
document.removeEventListener("keydown", escHandler);
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", escHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a success notification toast
|
||||
* @param {string} message - The success message
|
||||
*/
|
||||
function showAdminToast(message, type = "success") {
|
||||
// Create toast container if it doesn't exist
|
||||
let container = document.getElementById("adminToastContainer");
|
||||
if (!container) {
|
||||
container = document.createElement("div");
|
||||
container.id = "adminToastContainer";
|
||||
container.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 10001;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
`;
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
const toast = document.createElement("div");
|
||||
toast.style.cssText = `
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px 20px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
|
||||
transform: translateX(120%);
|
||||
transition: transform 0.3s ease;
|
||||
min-width: 280px;
|
||||
border-left: 4px solid ${
|
||||
type === "success" ? "#10b981" : type === "error" ? "#ef4444" : "#3b82f6"
|
||||
};
|
||||
`;
|
||||
|
||||
const icons = {
|
||||
success:
|
||||
'<i class="bi bi-check-circle-fill" style="color: #10b981; font-size: 1.25rem;"></i>',
|
||||
error:
|
||||
'<i class="bi bi-x-circle-fill" style="color: #ef4444; font-size: 1.25rem;"></i>',
|
||||
info: '<i class="bi bi-info-circle-fill" style="color: #3b82f6; font-size: 1.25rem;"></i>',
|
||||
};
|
||||
|
||||
toast.innerHTML = `
|
||||
${icons[type] || icons.info}
|
||||
<span style="flex: 1; color: #374151; font-size: 0.9rem;">${message}</span>
|
||||
`;
|
||||
|
||||
container.appendChild(toast);
|
||||
|
||||
// Animate in
|
||||
requestAnimationFrame(() => {
|
||||
toast.style.transform = "translateX(0)";
|
||||
});
|
||||
|
||||
// Auto remove after 4 seconds
|
||||
setTimeout(() => {
|
||||
toast.style.transform = "translateX(120%)";
|
||||
setTimeout(() => {
|
||||
toast.remove();
|
||||
}, 300);
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify frontend that data has changed
|
||||
* This updates a timestamp in localStorage that frontend pages check
|
||||
* @param {string} dataType - Type of data changed (products, pages, settings, etc.)
|
||||
*/
|
||||
function notifyFrontendChange(dataType = "all") {
|
||||
const timestamp = Date.now();
|
||||
const changeKey = `skyartshop_change_${dataType}`;
|
||||
|
||||
// Store in localStorage for cross-tab communication
|
||||
localStorage.setItem(changeKey, timestamp.toString());
|
||||
localStorage.setItem("skyartshop_last_change", timestamp.toString());
|
||||
|
||||
// Also broadcast to any open frontend tabs via BroadcastChannel
|
||||
try {
|
||||
const channel = new BroadcastChannel("skyartshop_updates");
|
||||
channel.postMessage({ type: dataType, timestamp });
|
||||
channel.close();
|
||||
} catch (e) {
|
||||
// BroadcastChannel not supported in some browsers
|
||||
}
|
||||
|
||||
console.log(`[Admin] Notified frontend of ${dataType} change`);
|
||||
}
|
||||
|
||||
// Export for use in other files
|
||||
window.showDeleteConfirm = showDeleteConfirm;
|
||||
window.showAdminToast = showAdminToast;
|
||||
window.notifyFrontendChange = notifyFrontendChange;
|
||||
@@ -103,98 +103,70 @@ function initializeQuillEditor() {
|
||||
});
|
||||
}
|
||||
|
||||
function openMediaLibraryForFeaturedImage() {
|
||||
// Create modal backdrop
|
||||
const backdrop = document.createElement("div");
|
||||
backdrop.id = "mediaLibraryBackdrop";
|
||||
backdrop.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.7);
|
||||
z-index: 9998;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
// Initialize media library
|
||||
let blogMediaLibrary = null;
|
||||
let galleryImages = [];
|
||||
|
||||
// Create modal container
|
||||
const modal = document.createElement("div");
|
||||
modal.id = "mediaLibraryModal";
|
||||
modal.style.cssText = `
|
||||
position: relative;
|
||||
width: 90%;
|
||||
max-width: 1200px;
|
||||
height: 85vh;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
|
||||
`;
|
||||
|
||||
// Create close button
|
||||
const closeBtn = document.createElement("button");
|
||||
closeBtn.innerHTML = '<i class="bi bi-x-lg"></i>';
|
||||
closeBtn.style.cssText = `
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
z-index: 10000;
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
closeBtn.onclick = closeMediaLibrary;
|
||||
|
||||
// Create iframe
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.id = "mediaLibraryFrame";
|
||||
iframe.src = "/admin/media-library.html?selectMode=true";
|
||||
iframe.style.cssText = `
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
`;
|
||||
|
||||
modal.appendChild(closeBtn);
|
||||
modal.appendChild(iframe);
|
||||
backdrop.appendChild(modal);
|
||||
document.body.appendChild(backdrop);
|
||||
|
||||
// Close on backdrop click
|
||||
backdrop.onclick = function (e) {
|
||||
if (e.target === backdrop) {
|
||||
closeMediaLibrary();
|
||||
}
|
||||
};
|
||||
|
||||
// Setup media selection handler
|
||||
window.handleMediaSelection = function (media) {
|
||||
const mediaItem = Array.isArray(media) ? media[0] : media;
|
||||
if (mediaItem && mediaItem.url) {
|
||||
document.getElementById("postFeaturedImage").value = mediaItem.url;
|
||||
updateFeaturedImagePreview(mediaItem.url);
|
||||
showToast("Featured image selected", "success");
|
||||
}
|
||||
closeMediaLibrary();
|
||||
};
|
||||
function initBlogMediaLibrary() {
|
||||
blogMediaLibrary = new MediaLibrary({
|
||||
selectMode: true,
|
||||
multiple: false,
|
||||
onSelect: function (media) {
|
||||
if (media && media.path) {
|
||||
document.getElementById("postFeaturedImage").value = media.path;
|
||||
updateFeaturedImagePreview(media.path);
|
||||
showToast("Featured image selected", "success");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function closeMediaLibrary() {
|
||||
const backdrop = document.getElementById("mediaLibraryBackdrop");
|
||||
if (backdrop) {
|
||||
backdrop.remove();
|
||||
function openMediaLibraryForFeaturedImage() {
|
||||
if (!blogMediaLibrary) {
|
||||
initBlogMediaLibrary();
|
||||
}
|
||||
blogMediaLibrary.options.multiple = false;
|
||||
blogMediaLibrary.options.onSelect = function (media) {
|
||||
if (media && media.path) {
|
||||
document.getElementById("postFeaturedImage").value = media.path;
|
||||
updateFeaturedImagePreview(media.path);
|
||||
showToast("Featured image selected", "success");
|
||||
}
|
||||
};
|
||||
blogMediaLibrary.open();
|
||||
}
|
||||
|
||||
function openMediaLibraryForGallery() {
|
||||
if (!blogMediaLibrary) {
|
||||
initBlogMediaLibrary();
|
||||
}
|
||||
blogMediaLibrary.options.multiple = true;
|
||||
blogMediaLibrary.options.onSelect = function (mediaList) {
|
||||
const items = Array.isArray(mediaList) ? mediaList : [mediaList];
|
||||
items.forEach((media) => {
|
||||
if (media && media.path && !galleryImages.includes(media.path)) {
|
||||
galleryImages.push(media.path);
|
||||
}
|
||||
});
|
||||
updateGalleryPreview();
|
||||
showToast(`${items.length} image(s) added to gallery`, "success");
|
||||
};
|
||||
blogMediaLibrary.open();
|
||||
}
|
||||
|
||||
function openMediaLibraryForVideo() {
|
||||
if (!blogMediaLibrary) {
|
||||
initBlogMediaLibrary();
|
||||
}
|
||||
blogMediaLibrary.options.multiple = false;
|
||||
blogMediaLibrary.options.onSelect = function (media) {
|
||||
if (media && media.path) {
|
||||
document.getElementById("postVideoUrl").value = media.path;
|
||||
updateVideoPreview(media.path);
|
||||
showToast("Video selected", "success");
|
||||
}
|
||||
};
|
||||
blogMediaLibrary.open();
|
||||
}
|
||||
|
||||
function updateFeaturedImagePreview(url) {
|
||||
@@ -202,21 +174,157 @@ function updateFeaturedImagePreview(url) {
|
||||
if (url) {
|
||||
preview.innerHTML = `
|
||||
<div style="position: relative; display: inline-block;">
|
||||
<img src="${url}" style="max-width: 200px; max-height: 150px; border-radius: 8px; border: 2px solid #e0e0e0;" />
|
||||
<img src="${url}" style="max-width: 100%; max-height: 150px; border-radius: 8px;" />
|
||||
<button type="button" onclick="removeFeaturedImage()" style="position: absolute; top: -8px; right: -8px; background: #dc3545; color: white; border: none; border-radius: 50%; width: 24px; height: 24px; cursor: pointer; font-size: 14px;">×</button>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
preview.innerHTML = "";
|
||||
preview.innerHTML =
|
||||
'<div class="text-muted text-center p-3"><i class="bi bi-image" style="font-size: 2rem;"></i><br><small>No image selected</small></div>';
|
||||
}
|
||||
}
|
||||
|
||||
function updateGalleryPreview() {
|
||||
const preview = document.getElementById("galleryImagesPreview");
|
||||
if (galleryImages.length === 0) {
|
||||
preview.innerHTML =
|
||||
'<div class="text-muted text-center p-3 w-100"><i class="bi bi-images" style="font-size: 2rem;"></i><br><small>No gallery images</small></div>';
|
||||
return;
|
||||
}
|
||||
preview.innerHTML = galleryImages
|
||||
.map(
|
||||
(img, idx) => `
|
||||
<div class="gallery-thumb">
|
||||
<img src="${img}" alt="Gallery ${idx + 1}" />
|
||||
<button type="button" class="remove-btn" onclick="removeGalleryImage(${idx})">×</button>
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
function removeGalleryImage(index) {
|
||||
galleryImages.splice(index, 1);
|
||||
updateGalleryPreview();
|
||||
showToast("Image removed from gallery", "info");
|
||||
}
|
||||
|
||||
function updateVideoPreview(url) {
|
||||
const preview = document.getElementById("videoPreview");
|
||||
if (url) {
|
||||
const isVideo = url.match(/\.(mp4|webm|mov|avi|mkv)$/i);
|
||||
if (isVideo) {
|
||||
preview.innerHTML = `
|
||||
<div style="position: relative; width: 100%;">
|
||||
<video controls style="max-width: 100%; max-height: 200px;">
|
||||
<source src="${url}" type="video/mp4">
|
||||
Your browser does not support video.
|
||||
</video>
|
||||
<button type="button" onclick="removeVideo()" style="position: absolute; top: 5px; right: 5px; background: #dc3545; color: white; border: none; border-radius: 50%; width: 28px; height: 28px; cursor: pointer;">×</button>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
preview.innerHTML = `<div class="video-placeholder"><i class="bi bi-link-45deg"></i>${url}</div>`;
|
||||
}
|
||||
} else {
|
||||
preview.innerHTML =
|
||||
'<div class="video-placeholder"><i class="bi bi-camera-video"></i>No video selected</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function removeVideo() {
|
||||
document.getElementById("postVideoUrl").value = "";
|
||||
document.getElementById("postExternalVideo").value = "";
|
||||
updateVideoPreview("");
|
||||
showToast("Video removed", "info");
|
||||
}
|
||||
|
||||
function removeFeaturedImage() {
|
||||
document.getElementById("postFeaturedImage").value = "";
|
||||
updateFeaturedImagePreview("");
|
||||
showToast("Featured image removed", "info");
|
||||
}
|
||||
|
||||
// Poll functions
|
||||
function togglePollSection() {
|
||||
const pollSection = document.getElementById("pollSection");
|
||||
const enabled = document.getElementById("enablePoll").checked;
|
||||
pollSection.style.display = enabled ? "block" : "none";
|
||||
}
|
||||
|
||||
function addPollOption() {
|
||||
const container = document.getElementById("pollOptionsContainer");
|
||||
const count = container.querySelectorAll(".poll-option-row").length + 1;
|
||||
const row = document.createElement("div");
|
||||
row.className = "input-group mb-2 poll-option-row";
|
||||
row.innerHTML = `
|
||||
<span class="input-group-text">${count}</span>
|
||||
<input type="text" class="form-control poll-option-input" placeholder="Option ${count}" />
|
||||
<button type="button" class="btn btn-outline-danger" onclick="removePollOption(this)">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
`;
|
||||
container.appendChild(row);
|
||||
}
|
||||
|
||||
function removePollOption(btn) {
|
||||
const row = btn.closest(".poll-option-row");
|
||||
row.remove();
|
||||
// Re-number options
|
||||
const container = document.getElementById("pollOptionsContainer");
|
||||
container.querySelectorAll(".poll-option-row").forEach((row, idx) => {
|
||||
row.querySelector(".input-group-text").textContent = idx + 1;
|
||||
});
|
||||
}
|
||||
|
||||
function getPollData() {
|
||||
if (!document.getElementById("enablePoll").checked) {
|
||||
return null;
|
||||
}
|
||||
const question = document.getElementById("pollQuestion").value.trim();
|
||||
const options = Array.from(document.querySelectorAll(".poll-option-input"))
|
||||
.map((input) => input.value.trim())
|
||||
.filter((v) => v);
|
||||
if (!question || options.length < 2) {
|
||||
return null;
|
||||
}
|
||||
return { question, options, votes: options.map(() => 0) };
|
||||
}
|
||||
|
||||
function loadPollData(poll) {
|
||||
if (poll && typeof poll === "object") {
|
||||
document.getElementById("enablePoll").checked = true;
|
||||
document.getElementById("pollSection").style.display = "block";
|
||||
document.getElementById("pollQuestion").value = poll.question || "";
|
||||
const container = document.getElementById("pollOptionsContainer");
|
||||
container.innerHTML = "";
|
||||
(poll.options || []).forEach((opt, idx) => {
|
||||
const row = document.createElement("div");
|
||||
row.className = "input-group mb-2 poll-option-row";
|
||||
row.innerHTML = `
|
||||
<span class="input-group-text">${idx + 1}</span>
|
||||
<input type="text" class="form-control poll-option-input" value="${opt}" />
|
||||
${idx >= 2 ? '<button type="button" class="btn btn-outline-danger" onclick="removePollOption(this)"><i class="bi bi-trash"></i></button>' : ""}
|
||||
`;
|
||||
container.appendChild(row);
|
||||
});
|
||||
} else {
|
||||
document.getElementById("enablePoll").checked = false;
|
||||
document.getElementById("pollSection").style.display = "none";
|
||||
document.getElementById("pollQuestion").value = "";
|
||||
document.getElementById("pollOptionsContainer").innerHTML = `
|
||||
<div class="input-group mb-2 poll-option-row">
|
||||
<span class="input-group-text">1</span>
|
||||
<input type="text" class="form-control poll-option-input" placeholder="Option 1" />
|
||||
</div>
|
||||
<div class="input-group mb-2 poll-option-row">
|
||||
<span class="input-group-text">2</span>
|
||||
<input type="text" class="form-control poll-option-input" placeholder="Option 2" />
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPosts() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/blog", { credentials: "include" });
|
||||
@@ -277,17 +385,17 @@ function renderPosts(posts) {
|
||||
<td>${formatDate(p.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editPost('${escapeHtml(
|
||||
String(p.id)
|
||||
String(p.id),
|
||||
)}')">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deletePost('${escapeHtml(
|
||||
String(p.id)
|
||||
String(p.id),
|
||||
)}', '${escapeHtml(p.title).replace(/'/g, "'")}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>`
|
||||
</tr>`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
@@ -297,7 +405,7 @@ function filterPosts() {
|
||||
const filtered = postsData.filter(
|
||||
(p) =>
|
||||
p.title.toLowerCase().includes(searchTerm) ||
|
||||
p.slug.toLowerCase().includes(searchTerm)
|
||||
p.slug.toLowerCase().includes(searchTerm),
|
||||
);
|
||||
renderPosts(filtered);
|
||||
}
|
||||
@@ -306,9 +414,15 @@ function showCreatePost() {
|
||||
document.getElementById("modalTitle").textContent = "Create Blog Post";
|
||||
document.getElementById("postForm").reset();
|
||||
document.getElementById("postId").value = "";
|
||||
document.getElementById("postPublished").checked = false;
|
||||
document.getElementById("postPublished").checked = true; // Default to published
|
||||
document.getElementById("postFeaturedImage").value = "";
|
||||
document.getElementById("postVideoUrl").value = "";
|
||||
document.getElementById("postExternalVideo").value = "";
|
||||
galleryImages = [];
|
||||
updateFeaturedImagePreview("");
|
||||
updateGalleryPreview();
|
||||
updateVideoPreview("");
|
||||
loadPollData(null);
|
||||
if (quillEditor) {
|
||||
quillEditor.setContents([]);
|
||||
}
|
||||
@@ -340,6 +454,28 @@ async function editPost(id) {
|
||||
document.getElementById("postFeaturedImage").value = featuredImage;
|
||||
updateFeaturedImagePreview(featuredImage);
|
||||
|
||||
// Set gallery images
|
||||
try {
|
||||
galleryImages = post.images ? JSON.parse(post.images) : [];
|
||||
} catch (e) {
|
||||
galleryImages = [];
|
||||
}
|
||||
updateGalleryPreview();
|
||||
|
||||
// Set video
|
||||
const videoUrl = post.videourl || "";
|
||||
document.getElementById("postVideoUrl").value = videoUrl;
|
||||
document.getElementById("postExternalVideo").value = "";
|
||||
updateVideoPreview(videoUrl);
|
||||
|
||||
// Set poll
|
||||
try {
|
||||
const poll = post.poll ? JSON.parse(post.poll) : null;
|
||||
loadPollData(poll);
|
||||
} catch (e) {
|
||||
loadPollData(null);
|
||||
}
|
||||
|
||||
document.getElementById("postMetaTitle").value = post.metatitle || "";
|
||||
document.getElementById("postMetaDescription").value =
|
||||
post.metadescription || "";
|
||||
@@ -359,12 +495,24 @@ async function savePost() {
|
||||
// Get content from Quill editor
|
||||
const content = quillEditor ? quillEditor.root.innerHTML : "";
|
||||
|
||||
// Get video URL (prefer uploaded, then external)
|
||||
let videoUrl = document.getElementById("postVideoUrl").value;
|
||||
if (!videoUrl) {
|
||||
videoUrl = document.getElementById("postExternalVideo").value;
|
||||
}
|
||||
|
||||
// Get poll data
|
||||
const poll = getPollData();
|
||||
|
||||
const formData = {
|
||||
title: document.getElementById("postTitle").value,
|
||||
slug: document.getElementById("postSlug").value,
|
||||
excerpt: document.getElementById("postExcerpt").value,
|
||||
content: content,
|
||||
featuredimage: document.getElementById("postFeaturedImage").value,
|
||||
images: JSON.stringify(galleryImages),
|
||||
videourl: videoUrl,
|
||||
poll: poll ? JSON.stringify(poll) : null,
|
||||
metatitle: document.getElementById("postMetaTitle").value,
|
||||
metadescription: document.getElementById("postMetaDescription").value,
|
||||
ispublished: document.getElementById("postPublished").checked,
|
||||
@@ -389,7 +537,7 @@ async function savePost() {
|
||||
if (data.success) {
|
||||
showToast(
|
||||
id ? "Post updated successfully" : "Post created successfully",
|
||||
"success"
|
||||
"success",
|
||||
);
|
||||
postModal.hide();
|
||||
loadPosts();
|
||||
@@ -403,23 +551,30 @@ async function savePost() {
|
||||
}
|
||||
|
||||
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) {
|
||||
showToast("Post deleted successfully", "success");
|
||||
loadPosts();
|
||||
} else {
|
||||
showToast(data.message || "Failed to delete post", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete post:", error);
|
||||
showToast("Failed to delete post", "error");
|
||||
}
|
||||
showDeleteConfirm(
|
||||
`Are you sure you want to delete "${title}"? This action cannot be undone.`,
|
||||
async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/blog/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
// Immediately remove from local array and re-render
|
||||
postsData = postsData.filter((p) => String(p.id) !== String(id));
|
||||
renderPosts(postsData);
|
||||
showToast("Post deleted successfully", "success");
|
||||
} else {
|
||||
showToast(data.message || "Failed to delete post", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete post:", error);
|
||||
showToast("Failed to delete post", "error");
|
||||
}
|
||||
},
|
||||
{ title: "Delete Blog Post", confirmText: "Delete Post" },
|
||||
);
|
||||
}
|
||||
|
||||
function showToast(message, type = "info") {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
582
website/admin/js/homepage.js.old
Normal file
582
website/admin/js/homepage.js.old
Normal file
@@ -0,0 +1,582 @@
|
||||
// Homepage Editor JavaScript
|
||||
|
||||
let homepageData = {};
|
||||
let quillEditors = {};
|
||||
let currentMediaPicker = null;
|
||||
|
||||
// Initialize Quill editors
|
||||
function initializeQuillEditors() {
|
||||
// Check if Quill is loaded
|
||||
if (typeof Quill === "undefined") {
|
||||
console.error("Quill.js is not loaded!");
|
||||
alert("Text editor failed to load. Please refresh the page.");
|
||||
return;
|
||||
}
|
||||
|
||||
const toolbarOptions = [
|
||||
["bold", "italic", "underline", "strike"],
|
||||
["blockquote", "code-block"],
|
||||
[{ header: 1 }, { header: 2 }],
|
||||
[{ list: "ordered" }, { list: "bullet" }],
|
||||
[{ script: "sub" }, { script: "super" }],
|
||||
[{ indent: "-1" }, { indent: "+1" }],
|
||||
[{ direction: "rtl" }],
|
||||
[{ size: ["small", false, "large", "huge"] }],
|
||||
[{ header: [1, 2, 3, 4, 5, 6, false] }],
|
||||
[{ color: [] }, { background: [] }],
|
||||
[{ font: [] }],
|
||||
[{ align: [] }],
|
||||
["link"],
|
||||
["clean"],
|
||||
];
|
||||
|
||||
try {
|
||||
// Initialize Quill for each description field
|
||||
quillEditors.hero = new Quill("#heroDescription", {
|
||||
theme: "snow",
|
||||
modules: { toolbar: toolbarOptions },
|
||||
placeholder: "Enter hero section description...",
|
||||
});
|
||||
|
||||
quillEditors.promotion = new Quill("#promotionDescription", {
|
||||
theme: "snow",
|
||||
modules: { toolbar: toolbarOptions },
|
||||
placeholder: "Enter promotion description...",
|
||||
});
|
||||
|
||||
quillEditors.portfolio = new Quill("#portfolioDescription", {
|
||||
theme: "snow",
|
||||
modules: { toolbar: toolbarOptions },
|
||||
placeholder: "Enter portfolio description...",
|
||||
});
|
||||
|
||||
console.log("Quill editors initialized successfully");
|
||||
} catch (error) {
|
||||
console.error("Error initializing Quill editors:", error);
|
||||
alert(
|
||||
"Failed to initialize text editors. Please check the console for errors."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
initializeQuillEditors();
|
||||
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadHomepageSettings();
|
||||
setupMediaLibraryListener();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Setup media library selection listener
|
||||
function setupMediaLibraryListener() {
|
||||
window.addEventListener("message", function (event) {
|
||||
// Security: verify origin if needed
|
||||
if (
|
||||
event.data &&
|
||||
event.data.type === "mediaSelected" &&
|
||||
currentMediaPicker
|
||||
) {
|
||||
const { section, field } = currentMediaPicker;
|
||||
handleMediaSelection(section, field, event.data.media);
|
||||
currentMediaPicker = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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 || {};
|
||||
|
||||
// If no data exists, load defaults from the frontend
|
||||
if (Object.keys(homepageData).length === 0) {
|
||||
console.log("No homepage data found, loading defaults from frontend");
|
||||
await loadDefaultsFromFrontend();
|
||||
}
|
||||
|
||||
populateFields();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load homepage settings:", error);
|
||||
// Load defaults if API fails
|
||||
await loadDefaultsFromFrontend();
|
||||
populateFields();
|
||||
}
|
||||
}
|
||||
|
||||
// Load default content from the current homepage
|
||||
async function loadDefaultsFromFrontend() {
|
||||
homepageData = {
|
||||
hero: {
|
||||
enabled: true,
|
||||
headline: "Welcome to Sky Art Shop",
|
||||
subheading: "Your destination for creative stationery and supplies",
|
||||
description:
|
||||
"<p>Discover our curated collection of scrapbooking, journaling, cardmaking, and collaging supplies. Express your creativity and bring your artistic vision to life.</p>",
|
||||
ctaText: "Shop Now",
|
||||
ctaLink: "/shop.html",
|
||||
backgroundUrl: "",
|
||||
layout: "text-left",
|
||||
},
|
||||
promotion: {
|
||||
enabled: true,
|
||||
title: "Get Inspired",
|
||||
description:
|
||||
"<p>At Sky Art Shop, we believe in the power of creativity to transform and inspire. Whether you're an experienced crafter or just beginning your creative journey, we have everything you need to bring your ideas to life.</p><p>Explore our collection of washi tapes, stickers, stamps, and more. Each item is carefully selected to help you create something beautiful and meaningful.</p>",
|
||||
imageUrl: "",
|
||||
imagePosition: "left",
|
||||
textAlignment: "left",
|
||||
},
|
||||
portfolio: {
|
||||
enabled: true,
|
||||
title: "Featured Products",
|
||||
description: "<p>Discover our most popular items</p>",
|
||||
count: 6,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function populateFields() {
|
||||
console.log("Populating fields with data:", homepageData);
|
||||
|
||||
// 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 || "";
|
||||
|
||||
if (homepageData.hero.description) {
|
||||
quillEditors.hero.root.innerHTML = homepageData.hero.description;
|
||||
}
|
||||
|
||||
document.getElementById("heroCtaText").value =
|
||||
homepageData.hero.ctaText || "";
|
||||
document.getElementById("heroCtaLink").value =
|
||||
homepageData.hero.ctaLink || "";
|
||||
|
||||
if (homepageData.hero.backgroundUrl) {
|
||||
document.getElementById("heroBackgroundUrl").value =
|
||||
homepageData.hero.backgroundUrl;
|
||||
displayMediaPreview(
|
||||
"hero",
|
||||
"background",
|
||||
homepageData.hero.backgroundUrl
|
||||
);
|
||||
}
|
||||
|
||||
if (homepageData.hero.layout) {
|
||||
const heroSection = document.getElementById("heroSection");
|
||||
heroSection.setAttribute("data-layout", homepageData.hero.layout);
|
||||
setActiveButton(`heroSection`, `layout-${homepageData.hero.layout}`);
|
||||
}
|
||||
|
||||
toggleSection("hero");
|
||||
}
|
||||
|
||||
// Promotion Section
|
||||
if (homepageData.promotion) {
|
||||
document.getElementById("promotionEnabled").checked =
|
||||
homepageData.promotion.enabled !== false;
|
||||
document.getElementById("promotionTitle").value =
|
||||
homepageData.promotion.title || "";
|
||||
|
||||
if (homepageData.promotion.description) {
|
||||
quillEditors.promotion.root.innerHTML =
|
||||
homepageData.promotion.description;
|
||||
}
|
||||
|
||||
if (homepageData.promotion.imageUrl) {
|
||||
document.getElementById("promotionImageUrl").value =
|
||||
homepageData.promotion.imageUrl;
|
||||
displayMediaPreview(
|
||||
"promotion",
|
||||
"image",
|
||||
homepageData.promotion.imageUrl
|
||||
);
|
||||
}
|
||||
|
||||
if (homepageData.promotion.imagePosition) {
|
||||
const promotionSection = document.getElementById("promotionSection");
|
||||
promotionSection.setAttribute(
|
||||
"data-image-position",
|
||||
homepageData.promotion.imagePosition
|
||||
);
|
||||
setActiveButton(
|
||||
`promotionSection`,
|
||||
`position-${homepageData.promotion.imagePosition}`
|
||||
);
|
||||
}
|
||||
|
||||
if (homepageData.promotion.textAlignment) {
|
||||
const promotionSection = document.getElementById("promotionSection");
|
||||
promotionSection.setAttribute(
|
||||
"data-text-alignment",
|
||||
homepageData.promotion.textAlignment
|
||||
);
|
||||
setActiveButton(
|
||||
`promotionSection`,
|
||||
`align-${homepageData.promotion.textAlignment}`
|
||||
);
|
||||
}
|
||||
|
||||
toggleSection("promotion");
|
||||
}
|
||||
|
||||
// Portfolio Section
|
||||
if (homepageData.portfolio) {
|
||||
document.getElementById("portfolioEnabled").checked =
|
||||
homepageData.portfolio.enabled !== false;
|
||||
document.getElementById("portfolioTitle").value =
|
||||
homepageData.portfolio.title || "";
|
||||
|
||||
if (homepageData.portfolio.description) {
|
||||
quillEditors.portfolio.root.innerHTML =
|
||||
homepageData.portfolio.description;
|
||||
}
|
||||
|
||||
document.getElementById("portfolioCount").value =
|
||||
homepageData.portfolio.count || 6;
|
||||
toggleSection("portfolio");
|
||||
}
|
||||
|
||||
// Show success message
|
||||
showSuccess(
|
||||
"Homepage content loaded! You can now edit and preview your changes."
|
||||
);
|
||||
}
|
||||
|
||||
function setActiveButton(sectionId, className) {
|
||||
const section = document.getElementById(sectionId);
|
||||
if (section) {
|
||||
const buttons = section.querySelectorAll(".alignment-btn");
|
||||
buttons.forEach((btn) => {
|
||||
if (btn.classList.contains(className)) {
|
||||
btn.classList.add("active");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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, button, select").forEach((el) => {
|
||||
el.disabled = false;
|
||||
});
|
||||
// Enable Quill editor
|
||||
if (quillEditors[sectionName]) {
|
||||
quillEditors[sectionName].enable();
|
||||
}
|
||||
} else {
|
||||
section.classList.add("disabled");
|
||||
content.querySelectorAll("input, button, select").forEach((el) => {
|
||||
el.disabled = true;
|
||||
});
|
||||
// Disable Quill editor
|
||||
if (quillEditors[sectionName]) {
|
||||
quillEditors[sectionName].disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Open media library in a modal
|
||||
function openMediaLibrary(section, field) {
|
||||
currentMediaPicker = { section, field };
|
||||
|
||||
// Create modal backdrop
|
||||
const backdrop = document.createElement("div");
|
||||
backdrop.id = "mediaLibraryBackdrop";
|
||||
backdrop.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.7);
|
||||
z-index: 9998;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
// Create modal container
|
||||
const modal = document.createElement("div");
|
||||
modal.id = "mediaLibraryModal";
|
||||
modal.style.cssText = `
|
||||
position: relative;
|
||||
width: 90%;
|
||||
max-width: 1200px;
|
||||
height: 85vh;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
|
||||
`;
|
||||
|
||||
// Create close button
|
||||
const closeBtn = document.createElement("button");
|
||||
closeBtn.innerHTML = '<i class="bi bi-x-lg"></i>';
|
||||
closeBtn.style.cssText = `
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
z-index: 10000;
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
closeBtn.onclick = closeMediaLibrary;
|
||||
|
||||
// Create iframe
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.id = "mediaLibraryFrame";
|
||||
iframe.src = "/admin/media-library.html?selectMode=true";
|
||||
iframe.style.cssText = `
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
`;
|
||||
|
||||
// Setup iframe message listener
|
||||
iframe.onload = function () {
|
||||
iframe.contentWindow.postMessage(
|
||||
{
|
||||
type: "initSelectMode",
|
||||
section: section,
|
||||
field: field,
|
||||
},
|
||||
"*"
|
||||
);
|
||||
};
|
||||
|
||||
modal.appendChild(closeBtn);
|
||||
modal.appendChild(iframe);
|
||||
backdrop.appendChild(modal);
|
||||
document.body.appendChild(backdrop);
|
||||
|
||||
// Close on backdrop click
|
||||
backdrop.onclick = function (e) {
|
||||
if (e.target === backdrop) {
|
||||
closeMediaLibrary();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function closeMediaLibrary() {
|
||||
const backdrop = document.getElementById("mediaLibraryBackdrop");
|
||||
if (backdrop) {
|
||||
backdrop.remove();
|
||||
}
|
||||
currentMediaPicker = null;
|
||||
}
|
||||
|
||||
function handleMediaSelection(section, field, media) {
|
||||
closeMediaLibrary();
|
||||
|
||||
const urlField = document.getElementById(
|
||||
`${section}${field === "background" ? "Background" : "Image"}Url`
|
||||
);
|
||||
if (urlField) {
|
||||
urlField.value = media.url;
|
||||
}
|
||||
|
||||
displayMediaPreview(section, field, media.url);
|
||||
|
||||
showSuccess(`Media selected successfully!`);
|
||||
}
|
||||
|
||||
function displayMediaPreview(section, field, url) {
|
||||
const previewId = `${section}Preview`;
|
||||
const preview = document.getElementById(previewId);
|
||||
const clearBtnId = `${section}${
|
||||
field === "background" ? "Background" : "Image"
|
||||
}Clear`;
|
||||
const clearBtn = document.getElementById(clearBtnId);
|
||||
|
||||
if (preview) {
|
||||
preview.classList.remove("empty");
|
||||
|
||||
// Check if it's a video
|
||||
const isVideo = url.match(/\.(mp4|webm|ogg)$/i);
|
||||
|
||||
if (isVideo) {
|
||||
preview.innerHTML = `<video src="${url}" style="max-width: 100%; max-height: 100%;" controls></video>`;
|
||||
} else {
|
||||
preview.innerHTML = `<img src="${url}" alt="Preview" />`;
|
||||
}
|
||||
}
|
||||
|
||||
if (clearBtn) {
|
||||
clearBtn.style.display = "inline-block";
|
||||
}
|
||||
}
|
||||
|
||||
function clearMedia(section, field) {
|
||||
const urlField = document.getElementById(
|
||||
`${section}${field === "background" ? "Background" : "Image"}Url`
|
||||
);
|
||||
if (urlField) {
|
||||
urlField.value = "";
|
||||
}
|
||||
|
||||
const previewId = `${section}Preview`;
|
||||
const preview = document.getElementById(previewId);
|
||||
if (preview) {
|
||||
preview.classList.add("empty");
|
||||
preview.innerHTML = '<i class="bi bi-image" style="font-size: 3rem"></i>';
|
||||
}
|
||||
|
||||
const clearBtnId = `${section}${
|
||||
field === "background" ? "Background" : "Image"
|
||||
}Clear`;
|
||||
const clearBtn = document.getElementById(clearBtnId);
|
||||
if (clearBtn) {
|
||||
clearBtn.style.display = "none";
|
||||
}
|
||||
|
||||
showSuccess("Media cleared");
|
||||
}
|
||||
|
||||
function setLayout(sectionName, layout) {
|
||||
const section = document.getElementById(`${sectionName}Section`);
|
||||
const buttons = section.querySelectorAll(".alignment-btn");
|
||||
buttons.forEach((btn) => btn.classList.remove("active"));
|
||||
event.target.closest(".alignment-btn").classList.add("active");
|
||||
|
||||
// Store in a data attribute
|
||||
section.setAttribute(`data-layout`, layout);
|
||||
}
|
||||
|
||||
function setImagePosition(sectionName, position) {
|
||||
const section = document.getElementById(`${sectionName}Section`);
|
||||
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");
|
||||
|
||||
section.setAttribute(`data-image-position`, position);
|
||||
}
|
||||
|
||||
function setTextAlignment(sectionName, alignment) {
|
||||
const section = document.getElementById(`${sectionName}Section`);
|
||||
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");
|
||||
|
||||
section.setAttribute(`data-text-alignment`, alignment);
|
||||
}
|
||||
|
||||
async function saveHomepage() {
|
||||
// Get hero layout
|
||||
const heroSection = document.getElementById("heroSection");
|
||||
const heroLayout = heroSection.getAttribute("data-layout") || "text-left";
|
||||
|
||||
// Get promotion layout settings
|
||||
const promotionSection = document.getElementById("promotionSection");
|
||||
const promotionImagePosition =
|
||||
promotionSection.getAttribute("data-image-position") || "left";
|
||||
const promotionTextAlignment =
|
||||
promotionSection.getAttribute("data-text-alignment") || "left";
|
||||
|
||||
const settings = {
|
||||
hero: {
|
||||
enabled: document.getElementById("heroEnabled").checked,
|
||||
headline: document.getElementById("heroHeadline").value,
|
||||
subheading: document.getElementById("heroSubheading").value,
|
||||
description: quillEditors.hero.root.innerHTML,
|
||||
ctaText: document.getElementById("heroCtaText").value,
|
||||
ctaLink: document.getElementById("heroCtaLink").value,
|
||||
backgroundUrl: document.getElementById("heroBackgroundUrl")?.value || "",
|
||||
layout: heroLayout,
|
||||
},
|
||||
promotion: {
|
||||
enabled: document.getElementById("promotionEnabled").checked,
|
||||
title: document.getElementById("promotionTitle").value,
|
||||
description: quillEditors.promotion.root.innerHTML,
|
||||
imageUrl: document.getElementById("promotionImageUrl")?.value || "",
|
||||
imagePosition: promotionImagePosition,
|
||||
textAlignment: promotionTextAlignment,
|
||||
},
|
||||
portfolio: {
|
||||
enabled: document.getElementById("portfolioEnabled").checked,
|
||||
title: document.getElementById("portfolioTitle").value,
|
||||
description: quillEditors.portfolio.root.innerHTML,
|
||||
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 on the frontend."
|
||||
);
|
||||
homepageData = settings;
|
||||
} else {
|
||||
showError(data.message || "Failed to save homepage settings");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save homepage:", error);
|
||||
showError("Failed to save homepage settings");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
1131
website/admin/js/media-library.js
Normal file
1131
website/admin/js/media-library.js
Normal file
File diff suppressed because it is too large
Load Diff
1683
website/admin/js/pages-new.js
Normal file
1683
website/admin/js/pages-new.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,12 +5,29 @@ let pageModal;
|
||||
let quillEditor;
|
||||
let aboutContentEditor;
|
||||
let aboutTeamMembers = [];
|
||||
let deletedTeamMemberIds = []; // Track deleted team members for database deletion
|
||||
let currentMediaPicker = null;
|
||||
let pagesMediaLibrary = null;
|
||||
|
||||
// Initialize pages media library
|
||||
function initPagesMediaLibrary() {
|
||||
if (typeof MediaLibrary !== "undefined" && !pagesMediaLibrary) {
|
||||
pagesMediaLibrary = new MediaLibrary({
|
||||
selectMode: true,
|
||||
multiple: false,
|
||||
onSelect: function (media) {
|
||||
handleMediaSelection(media);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
pageModal = new bootstrap.Modal(document.getElementById("pageModal"));
|
||||
initializeQuillEditor();
|
||||
initializeAboutEditor();
|
||||
initPagesMediaLibrary();
|
||||
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadPages();
|
||||
@@ -30,13 +47,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
});
|
||||
});
|
||||
|
||||
// Media Library Selection Handler
|
||||
window.addEventListener("message", function (event) {
|
||||
if (event.data.type === "mediaSelected" && currentMediaPicker) {
|
||||
handleMediaSelection(event.data.media);
|
||||
}
|
||||
});
|
||||
|
||||
function initializeQuillEditor() {
|
||||
quillEditor = new Quill("#pageContentEditor", {
|
||||
theme: "snow",
|
||||
@@ -57,6 +67,31 @@ function initializeQuillEditor() {
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// Custom image handler to use media library
|
||||
const toolbar = quillEditor.getModule("toolbar");
|
||||
toolbar.addHandler("image", function () {
|
||||
openMediaLibraryForPageEditor();
|
||||
});
|
||||
}
|
||||
|
||||
// Open media library for main page editor image insertion
|
||||
function openMediaLibraryForPageEditor() {
|
||||
if (!pagesMediaLibrary) {
|
||||
initPagesMediaLibrary();
|
||||
}
|
||||
|
||||
currentMediaPicker = "pageEditorImage";
|
||||
pagesMediaLibrary.show();
|
||||
}
|
||||
|
||||
// Handle media selection for main page editor
|
||||
function handlePageEditorImageSelection(media) {
|
||||
if (quillEditor && media && media.path) {
|
||||
const range = quillEditor.getSelection(true);
|
||||
quillEditor.insertEmbed(range.index, "image", media.path);
|
||||
quillEditor.setSelection(range.index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
function initializeAboutEditor() {
|
||||
@@ -75,6 +110,31 @@ function initializeAboutEditor() {
|
||||
},
|
||||
placeholder: "Write your page content here...",
|
||||
});
|
||||
|
||||
// Custom image handler to use media library
|
||||
const toolbar = aboutContentEditor.getModule("toolbar");
|
||||
toolbar.addHandler("image", function () {
|
||||
openMediaLibraryForAboutEditor();
|
||||
});
|
||||
}
|
||||
|
||||
// Open media library for About editor image insertion
|
||||
function openMediaLibraryForAboutEditor() {
|
||||
if (!pagesMediaLibrary) {
|
||||
initPagesMediaLibrary();
|
||||
}
|
||||
|
||||
currentMediaPicker = "aboutEditorImage";
|
||||
pagesMediaLibrary.show();
|
||||
}
|
||||
|
||||
// Handle media selection for About editor
|
||||
function handleAboutEditorImageSelection(media) {
|
||||
if (aboutContentEditor && media && media.path) {
|
||||
const range = aboutContentEditor.getSelection(true);
|
||||
aboutContentEditor.insertEmbed(range.index, "image", media.path);
|
||||
aboutContentEditor.setSelection(range.index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPages() {
|
||||
@@ -120,17 +180,17 @@ function renderPages(pages) {
|
||||
<td>${formatDate(p.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editPage('${escapeHtml(
|
||||
p.id
|
||||
p.id,
|
||||
)}')">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deletePage('${escapeHtml(
|
||||
p.id
|
||||
p.id,
|
||||
)}', '${escapeHtml(p.title).replace(/'/g, "\\'")}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>`
|
||||
</tr>`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
@@ -140,7 +200,7 @@ function filterPages() {
|
||||
const filtered = pagesData.filter(
|
||||
(p) =>
|
||||
p.title.toLowerCase().includes(searchTerm) ||
|
||||
p.slug.toLowerCase().includes(searchTerm)
|
||||
p.slug.toLowerCase().includes(searchTerm),
|
||||
);
|
||||
renderPages(filtered);
|
||||
}
|
||||
@@ -155,6 +215,7 @@ function showCreatePage() {
|
||||
// Show regular editor by default
|
||||
document.getElementById("contactStructuredFields").style.display = "none";
|
||||
document.getElementById("aboutWithTeamFields").style.display = "none";
|
||||
document.getElementById("privacyContentSection").style.display = "none";
|
||||
document.getElementById("regularContentEditor").style.display = "block";
|
||||
|
||||
pageModal.show();
|
||||
@@ -203,6 +264,40 @@ async function editPage(id) {
|
||||
) {
|
||||
// Show About page with team members
|
||||
await showAboutWithTeamFields(page);
|
||||
} else if (
|
||||
page.slug === "privacy" ||
|
||||
page.slug === "page-privacy" ||
|
||||
page.slug.includes("privacy") ||
|
||||
page.slug === "shipping" ||
|
||||
page.slug === "shipping-info" ||
|
||||
page.slug.includes("shipping") ||
|
||||
page.slug === "returns" ||
|
||||
page.slug.includes("return")
|
||||
) {
|
||||
// Show Privacy/Shipping/Returns page with structured fields
|
||||
if (page.pagedata) {
|
||||
showPrivacyStructuredFields(page.pagedata);
|
||||
} else {
|
||||
// No pagedata, use regular editor
|
||||
document.getElementById("contactStructuredFields").style.display =
|
||||
"none";
|
||||
document.getElementById("aboutWithTeamFields").style.display = "none";
|
||||
document.getElementById("privacyContentSection").style.display =
|
||||
"none";
|
||||
document.getElementById("regularContentEditor").style.display =
|
||||
"block";
|
||||
|
||||
if (page.content) {
|
||||
try {
|
||||
const delta = JSON.parse(page.content);
|
||||
quillEditor.setContents(delta);
|
||||
} catch {
|
||||
quillEditor.clipboard.dangerouslyPasteHTML(page.content);
|
||||
}
|
||||
} else {
|
||||
quillEditor.setContents([]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use regular Quill editor for all other pages (privacy, etc)
|
||||
document.getElementById("contactStructuredFields").style.display =
|
||||
@@ -286,7 +381,7 @@ function renderBusinessHours(hours) {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
@@ -321,6 +416,124 @@ function removeBusinessHour(index) {
|
||||
}
|
||||
}
|
||||
|
||||
// Privacy Policy Page Functions
|
||||
let privacySectionEditors = []; // Array to store Quill editors for each section
|
||||
|
||||
function showPrivacyStructuredFields(pagedata) {
|
||||
// Hide regular editor, show privacy fields
|
||||
document.getElementById("regularContentEditor").style.display = "none";
|
||||
document.getElementById("aboutWithTeamFields").style.display = "none";
|
||||
document.getElementById("contactStructuredFields").style.display = "none";
|
||||
document.getElementById("privacyContentSection").style.display = "block";
|
||||
|
||||
// Populate header fields
|
||||
if (pagedata.header) {
|
||||
document.getElementById("privacyHeaderTitle").value =
|
||||
pagedata.header.title || "";
|
||||
}
|
||||
|
||||
// Populate last updated
|
||||
document.getElementById("privacyLastUpdated").value =
|
||||
pagedata.lastUpdated || "";
|
||||
|
||||
// Populate sections
|
||||
if (pagedata.sections && pagedata.sections.length > 0) {
|
||||
renderPrivacySections(pagedata.sections);
|
||||
} else {
|
||||
// Start with one empty section
|
||||
privacySectionEditors = [];
|
||||
document.getElementById("privacySectionsContainer").innerHTML = "";
|
||||
}
|
||||
}
|
||||
|
||||
function renderPrivacySections(sections) {
|
||||
const container = document.getElementById("privacySectionsContainer");
|
||||
privacySectionEditors = []; // Reset editors array
|
||||
container.innerHTML = "";
|
||||
|
||||
sections.forEach((section, index) => {
|
||||
addPrivacySectionWithData(section, index);
|
||||
});
|
||||
}
|
||||
|
||||
function addPrivacySection() {
|
||||
addPrivacySectionWithData(
|
||||
{ title: "", content: "" },
|
||||
privacySectionEditors.length,
|
||||
);
|
||||
}
|
||||
|
||||
function addPrivacySectionWithData(section, index) {
|
||||
const container = document.getElementById("privacySectionsContainer");
|
||||
const sectionDiv = document.createElement("div");
|
||||
sectionDiv.className = "contact-field-group mb-4";
|
||||
sectionDiv.setAttribute("data-section-index", index);
|
||||
|
||||
// Create unique IDs for this section's editor
|
||||
const editorId = `privacySectionContent_${index}`;
|
||||
|
||||
sectionDiv.innerHTML = `
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h6><i class="bi bi-file-text"></i> Section ${index + 1}</h6>
|
||||
<button type="button" class="btn btn-sm btn-danger" onclick="removePrivacySection(${index})">
|
||||
<i class="bi bi-trash"></i> Remove
|
||||
</button>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Section Title</label>
|
||||
<input type="text" class="form-control"
|
||||
value="${escapeHtml(section.title || "")}"
|
||||
data-field="title"
|
||||
placeholder="e.g., Information We Collect">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Content</label>
|
||||
<div class="editor-wrapper">
|
||||
<div id="${editorId}" style="min-height: 200px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.appendChild(sectionDiv);
|
||||
|
||||
// Initialize Quill editor for this section's content
|
||||
const editor = new Quill(`#${editorId}`, {
|
||||
theme: "snow",
|
||||
modules: {
|
||||
toolbar: [
|
||||
[{ header: [2, 3, false] }],
|
||||
["bold", "italic", "underline"],
|
||||
[{ list: "ordered" }, { list: "bullet" }],
|
||||
["link"],
|
||||
["clean"],
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// Set the content if provided
|
||||
if (section.content) {
|
||||
try {
|
||||
const delta = JSON.parse(section.content);
|
||||
editor.setContents(delta);
|
||||
} catch {
|
||||
// If it's plain text/HTML, set it directly
|
||||
editor.clipboard.dangerouslyPasteHTML(section.content);
|
||||
}
|
||||
}
|
||||
|
||||
privacySectionEditors[index] = editor;
|
||||
}
|
||||
|
||||
function removePrivacySection(index) {
|
||||
const container = document.getElementById("privacySectionsContainer");
|
||||
const sectionDiv = container.querySelector(`[data-section-index="${index}"]`);
|
||||
if (sectionDiv) {
|
||||
sectionDiv.remove();
|
||||
// Remove editor from array (set to null to keep indices)
|
||||
privacySectionEditors[index] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// About Page with Team Members Functions
|
||||
async function showAboutWithTeamFields(page) {
|
||||
// Hide other editors
|
||||
@@ -346,7 +559,12 @@ async function showAboutWithTeamFields(page) {
|
||||
|
||||
async function loadTeamMembersForAbout() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/team-members");
|
||||
// Reset the deleted IDs when loading fresh data
|
||||
deletedTeamMemberIds = [];
|
||||
|
||||
const response = await fetch("/api/admin/team-members", {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success && data.teamMembers) {
|
||||
aboutTeamMembers = data.teamMembers;
|
||||
@@ -387,7 +605,7 @@ function displayTeamMembersInEditor() {
|
||||
${
|
||||
member.image_url
|
||||
? `<img src="${member.image_url}" alt="${escapeHtml(
|
||||
member.name
|
||||
member.name,
|
||||
)}" />`
|
||||
: `<i class="bi bi-person-circle"></i>`
|
||||
}
|
||||
@@ -409,8 +627,8 @@ function displayTeamMembersInEditor() {
|
||||
rows="2"
|
||||
placeholder="Bio"
|
||||
onchange="updateTeamMember(${index}, 'bio', this.value)">${escapeHtml(
|
||||
member.bio || ""
|
||||
)}</textarea>
|
||||
member.bio || "",
|
||||
)}</textarea>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<div class="input-group input-group-sm">
|
||||
@@ -431,7 +649,7 @@ function displayTeamMembersInEditor() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
@@ -456,117 +674,71 @@ function updateTeamMember(index, field, value) {
|
||||
}
|
||||
|
||||
function removeTeamMemberFromAbout(index) {
|
||||
const member = aboutTeamMembers[index];
|
||||
// If member has an ID, track it for deletion from database
|
||||
if (member && member.id) {
|
||||
deletedTeamMemberIds.push(member.id);
|
||||
}
|
||||
aboutTeamMembers.splice(index, 1);
|
||||
displayTeamMembersInEditor();
|
||||
}
|
||||
|
||||
function selectImageForMember(index) {
|
||||
currentMediaPicker = { purpose: "teamMember", index };
|
||||
openMediaLibraryModal();
|
||||
}
|
||||
|
||||
function openMediaLibraryModal() {
|
||||
// Create modal backdrop
|
||||
const backdrop = document.createElement("div");
|
||||
backdrop.id = "mediaLibraryBackdrop";
|
||||
backdrop.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.7);
|
||||
z-index: 9998;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
// Initialize if not already
|
||||
initPagesMediaLibrary();
|
||||
|
||||
// Create modal container
|
||||
const modal = document.createElement("div");
|
||||
modal.id = "mediaLibraryModal";
|
||||
modal.style.cssText = `
|
||||
position: relative;
|
||||
width: 90%;
|
||||
max-width: 1200px;
|
||||
height: 85vh;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
|
||||
`;
|
||||
|
||||
// Create close button
|
||||
const closeBtn = document.createElement("button");
|
||||
closeBtn.innerHTML = '<i class="bi bi-x-lg"></i>';
|
||||
closeBtn.style.cssText = `
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
z-index: 10000;
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
closeBtn.onclick = closeMediaLibraryModal;
|
||||
|
||||
// Create iframe
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.id = "mediaLibraryFrame";
|
||||
iframe.src = "/admin/media-library.html?selectMode=true";
|
||||
iframe.style.cssText = `
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
`;
|
||||
|
||||
modal.appendChild(closeBtn);
|
||||
modal.appendChild(iframe);
|
||||
backdrop.appendChild(modal);
|
||||
document.body.appendChild(backdrop);
|
||||
|
||||
// Close on backdrop click
|
||||
backdrop.onclick = function (e) {
|
||||
if (e.target === backdrop) {
|
||||
closeMediaLibraryModal();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function closeMediaLibraryModal() {
|
||||
const backdrop = document.getElementById("mediaLibraryBackdrop");
|
||||
if (backdrop) {
|
||||
backdrop.remove();
|
||||
if (pagesMediaLibrary) {
|
||||
pagesMediaLibrary.open();
|
||||
}
|
||||
currentMediaPicker = null;
|
||||
}
|
||||
|
||||
function handleMediaSelection(media) {
|
||||
if (!currentMediaPicker) return;
|
||||
|
||||
// Handle About editor image insertion
|
||||
if (currentMediaPicker === "aboutEditorImage") {
|
||||
handleAboutEditorImageSelection(media);
|
||||
currentMediaPicker = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle main page editor image insertion
|
||||
if (currentMediaPicker === "pageEditorImage") {
|
||||
handlePageEditorImageSelection(media);
|
||||
currentMediaPicker = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentMediaPicker.purpose === "teamMember") {
|
||||
const index = currentMediaPicker.index;
|
||||
if (aboutTeamMembers[index]) {
|
||||
// Media is an array, get the first item's URL
|
||||
const selectedMedia = Array.isArray(media) ? media[0] : media;
|
||||
aboutTeamMembers[index].image_url = selectedMedia.url;
|
||||
aboutTeamMembers[index].image_url = media.path;
|
||||
displayTeamMembersInEditor();
|
||||
}
|
||||
}
|
||||
|
||||
closeMediaLibraryModal();
|
||||
currentMediaPicker = null;
|
||||
}
|
||||
|
||||
async function saveTeamMembers() {
|
||||
try {
|
||||
// First, delete any removed team members from the database
|
||||
for (const memberId of deletedTeamMemberIds) {
|
||||
try {
|
||||
await fetch(`/api/admin/team-members/${memberId}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
console.log(`Deleted team member ${memberId}`);
|
||||
} catch (err) {
|
||||
console.error(`Failed to delete team member ${memberId}:`, err);
|
||||
}
|
||||
}
|
||||
// Clear the deleted IDs array after processing
|
||||
deletedTeamMemberIds = [];
|
||||
|
||||
// Save or update each team member
|
||||
for (const member of aboutTeamMembers) {
|
||||
if (!member.name || !member.position) continue; // Skip incomplete members
|
||||
@@ -584,6 +756,7 @@ async function saveTeamMembers() {
|
||||
await fetch(`/api/admin/team-members/${member.id}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
} else {
|
||||
@@ -591,6 +764,7 @@ async function saveTeamMembers() {
|
||||
const response = await fetch("/api/admin/team-members", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const data = await response.json();
|
||||
@@ -599,6 +773,7 @@ async function saveTeamMembers() {
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log("Team members saved successfully");
|
||||
} catch (error) {
|
||||
console.error("Error saving team members:", error);
|
||||
}
|
||||
@@ -663,6 +838,52 @@ async function savePage() {
|
||||
formData.pagedata = pagedata;
|
||||
formData.content = generatedHTML; // Store HTML in content field
|
||||
formData.contenthtml = generatedHTML; // Also in contenthtml
|
||||
}
|
||||
// Check if this is privacy/shipping/returns page with structured fields
|
||||
else if (
|
||||
(slug.includes("privacy") ||
|
||||
slug.includes("shipping") ||
|
||||
slug.includes("return")) &&
|
||||
document.getElementById("privacyContentSection").style.display !== "none"
|
||||
) {
|
||||
// Collect structured privacy data
|
||||
const pagedata = {
|
||||
header: {
|
||||
title: document.getElementById("privacyHeaderTitle").value,
|
||||
},
|
||||
lastUpdated: document.getElementById("privacyLastUpdated").value,
|
||||
sections: [],
|
||||
};
|
||||
|
||||
// Collect sections
|
||||
const sectionDivs = document.getElementById(
|
||||
"privacySectionsContainer",
|
||||
).children;
|
||||
for (let i = 0; i < sectionDivs.length; i++) {
|
||||
const sectionDiv = sectionDivs[i];
|
||||
const title = sectionDiv.querySelector('[data-field="title"]').value;
|
||||
const editor = privacySectionEditors[i];
|
||||
|
||||
if (editor && title) {
|
||||
// Get content as Delta JSON
|
||||
const contentDelta = editor.getContents();
|
||||
const contentHTML = editor.root.innerHTML;
|
||||
|
||||
pagedata.sections.push({
|
||||
title: title,
|
||||
content: JSON.stringify(contentDelta), // Store as Delta
|
||||
contentHTML: contentHTML, // Also store rendered HTML
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Store the structured data
|
||||
formData.pagedata = pagedata;
|
||||
|
||||
// Also generate and store as content for backwards compatibility
|
||||
const generatedHTML = generatePrivacyHTML(pagedata);
|
||||
formData.content = generatedHTML;
|
||||
formData.contenthtml = generatedHTML;
|
||||
} else {
|
||||
// Use Quill editor content for other pages
|
||||
const contentDelta = quillEditor.getContents();
|
||||
@@ -695,7 +916,7 @@ async function savePage() {
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess(
|
||||
id ? "Page updated successfully" : "Page created successfully"
|
||||
id ? "Page updated successfully" : "Page created successfully",
|
||||
);
|
||||
pageModal.hide();
|
||||
loadPages();
|
||||
@@ -717,11 +938,11 @@ function generateContactHTML(pagedata) {
|
||||
(hour) => `
|
||||
<div>
|
||||
<p style="font-weight: 600; margin-bottom: 8px;">${escapeHtml(
|
||||
hour.days
|
||||
hour.days,
|
||||
)}</p>
|
||||
<p style="opacity: 0.95; margin: 0;">${escapeHtml(hour.hours)}</p>
|
||||
</div>
|
||||
`
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
|
||||
@@ -743,7 +964,7 @@ function generateContactHTML(pagedata) {
|
||||
</div>
|
||||
<h3 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 12px;">Phone</h3>
|
||||
<p style="font-size: 1rem; opacity: 0.9; margin: 0;">${escapeHtml(
|
||||
contactInfo.phone
|
||||
contactInfo.phone,
|
||||
)}</p>
|
||||
</div>
|
||||
|
||||
@@ -754,7 +975,7 @@ function generateContactHTML(pagedata) {
|
||||
</div>
|
||||
<h3 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 12px;">Email</h3>
|
||||
<p style="font-size: 1rem; opacity: 0.9; margin: 0;">${escapeHtml(
|
||||
contactInfo.email
|
||||
contactInfo.email,
|
||||
)}</p>
|
||||
</div>
|
||||
|
||||
@@ -765,7 +986,7 @@ function generateContactHTML(pagedata) {
|
||||
</div>
|
||||
<h3 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 12px;">Location</h3>
|
||||
<p style="font-size: 1rem; opacity: 0.9; margin: 0;">${escapeHtml(
|
||||
contactInfo.address
|
||||
contactInfo.address,
|
||||
)}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -780,11 +1001,43 @@ function generateContactHTML(pagedata) {
|
||||
`;
|
||||
}
|
||||
|
||||
function generatePrivacyHTML(pagedata) {
|
||||
const { header, lastUpdated, sections } = pagedata;
|
||||
|
||||
// Generate sections HTML
|
||||
const sectionsHTML = sections
|
||||
.map((section) => {
|
||||
// Parse the content Delta if it's stored as JSON
|
||||
let contentHTML = section.contentHTML;
|
||||
if (!contentHTML && section.content) {
|
||||
try {
|
||||
// If we don't have contentHTML, try to get it from the content field
|
||||
contentHTML = section.content;
|
||||
} catch {
|
||||
contentHTML = section.content;
|
||||
}
|
||||
}
|
||||
|
||||
return `
|
||||
<h2>${escapeHtml(section.title)}</h2>
|
||||
<div class="section-content">${contentHTML}</div>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
return `
|
||||
<div style="max-width: 900px; margin: 0 auto;">
|
||||
${lastUpdated ? `<p class="policy-meta" style="color: #636e72; font-style: italic; margin-bottom: 24px;">Last updated: ${escapeHtml(lastUpdated)}</p>` : ""}
|
||||
${sectionsHTML}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function deletePage(id, title) {
|
||||
// Show custom confirmation modal instead of browser confirm
|
||||
showConfirmation(
|
||||
`Are you sure you want to delete "<strong>${escapeHtml(
|
||||
title
|
||||
title,
|
||||
)}</strong>"?<br><br>` +
|
||||
`<small class="text-muted">This action cannot be undone.</small>`,
|
||||
async () => {
|
||||
@@ -804,7 +1057,7 @@ async function deletePage(id, title) {
|
||||
console.error("Failed to delete page:", error);
|
||||
showError("Failed to delete page");
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -845,7 +1098,7 @@ function showError(message) {
|
||||
|
||||
function showNotification(message, type) {
|
||||
const modal = new bootstrap.Modal(
|
||||
document.getElementById("notificationModal")
|
||||
document.getElementById("notificationModal"),
|
||||
);
|
||||
const modalContent = document.getElementById("notificationModalContent");
|
||||
const modalHeader = document.getElementById("notificationModalHeader");
|
||||
@@ -863,7 +1116,7 @@ function showNotification(message, type) {
|
||||
modalIcon.className = "bi bi-check-circle-fill me-2";
|
||||
modalTitleText.textContent = "Success";
|
||||
modalBody.innerHTML = `<p class="mb-0"><i class="bi bi-check-circle text-success me-2"></i>${escapeHtml(
|
||||
message
|
||||
message,
|
||||
)}</p>`;
|
||||
} else {
|
||||
modalContent.classList.remove("border-success");
|
||||
@@ -874,7 +1127,7 @@ function showNotification(message, type) {
|
||||
modalIcon.className = "bi bi-exclamation-triangle-fill me-2";
|
||||
modalTitleText.textContent = "Error";
|
||||
modalBody.innerHTML = `<p class="mb-0"><i class="bi bi-x-circle text-danger me-2"></i>${escapeHtml(
|
||||
message
|
||||
message,
|
||||
)}</p>`;
|
||||
}
|
||||
|
||||
@@ -1061,7 +1314,7 @@ function toggleContentExpand(editorType) {
|
||||
"Resize handle clicked! Target:",
|
||||
targetId,
|
||||
"Element found:",
|
||||
!!targetElement
|
||||
!!targetElement,
|
||||
);
|
||||
|
||||
if (!targetElement) {
|
||||
@@ -1092,7 +1345,7 @@ function toggleContentExpand(editorType) {
|
||||
const deltaY = e.clientY - resizeState.startY;
|
||||
const newHeight = Math.max(
|
||||
200,
|
||||
Math.min(1200, resizeState.startHeight + deltaY)
|
||||
Math.min(1200, resizeState.startHeight + deltaY),
|
||||
);
|
||||
|
||||
// Update target element height
|
||||
@@ -1110,7 +1363,7 @@ function toggleContentExpand(editorType) {
|
||||
"pageContentEditor resize - editor:",
|
||||
!!editor,
|
||||
"toolbar:",
|
||||
!!toolbar
|
||||
!!toolbar,
|
||||
);
|
||||
|
||||
if (editor && toolbar) {
|
||||
@@ -1123,7 +1376,7 @@ function toggleContentExpand(editorType) {
|
||||
"editor:",
|
||||
editorHeight,
|
||||
"total:",
|
||||
newHeight
|
||||
newHeight,
|
||||
);
|
||||
|
||||
resizeState.target.style.height = editorHeight + "px";
|
||||
@@ -1147,7 +1400,7 @@ function toggleContentExpand(editorType) {
|
||||
"aboutContentEditor resize - editor:",
|
||||
!!editor,
|
||||
"toolbar:",
|
||||
!!toolbar
|
||||
!!toolbar,
|
||||
);
|
||||
|
||||
if (editor && toolbar) {
|
||||
@@ -1160,7 +1413,7 @@ function toggleContentExpand(editorType) {
|
||||
"editor:",
|
||||
editorHeight,
|
||||
"total:",
|
||||
newHeight
|
||||
newHeight,
|
||||
);
|
||||
|
||||
resizeState.target.style.height = editorHeight + "px";
|
||||
|
||||
@@ -6,6 +6,20 @@ let quillEditor;
|
||||
let portfolioImages = [];
|
||||
let currentMediaPicker = null;
|
||||
let isModalExpanded = false;
|
||||
let portfolioMediaLibrary = null;
|
||||
|
||||
// Initialize portfolio media library
|
||||
function initPortfolioMediaLibrary() {
|
||||
if (typeof MediaLibrary !== "undefined" && !portfolioMediaLibrary) {
|
||||
portfolioMediaLibrary = new MediaLibrary({
|
||||
selectMode: true,
|
||||
multiple: true, // Allow multiple image selection for portfolio gallery
|
||||
onSelect: function (media) {
|
||||
handleMediaSelection(media);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
projectModal = new bootstrap.Modal(document.getElementById("projectModal"));
|
||||
@@ -19,6 +33,9 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
// Initialize Quill editor
|
||||
initializeQuillEditor();
|
||||
|
||||
// Initialize media library
|
||||
initPortfolioMediaLibrary();
|
||||
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadProjects();
|
||||
@@ -123,7 +140,7 @@ async function loadProjects() {
|
||||
title: p.title,
|
||||
isactive: p.isactive,
|
||||
isactiveType: typeof p.isactive,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
renderProjects(projectsData);
|
||||
}
|
||||
@@ -152,7 +169,7 @@ function renderProjects(projects) {
|
||||
console.log(
|
||||
`Project ${p.id}: isactive =`,
|
||||
p.isactive,
|
||||
`(type: ${typeof p.isactive})`
|
||||
`(type: ${typeof p.isactive})`,
|
||||
);
|
||||
const isActive =
|
||||
p.isactive === true || p.isactive === "true" || p.isactive === 1;
|
||||
@@ -174,12 +191,12 @@ function renderProjects(projects) {
|
||||
<td>${formatDate(p.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editProject('${escapeHtml(
|
||||
String(p.id)
|
||||
String(p.id),
|
||||
)}')">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteProject('${escapeHtml(
|
||||
String(p.id)
|
||||
String(p.id),
|
||||
)}', '${escapeHtml(p.title).replace(/'/g, "'")}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
@@ -192,7 +209,7 @@ function renderProjects(projects) {
|
||||
function filterProjects() {
|
||||
const searchTerm = document.getElementById("searchInput").value.toLowerCase();
|
||||
const filtered = projectsData.filter((p) =>
|
||||
p.title.toLowerCase().includes(searchTerm)
|
||||
p.title.toLowerCase().includes(searchTerm),
|
||||
);
|
||||
renderProjects(filtered);
|
||||
}
|
||||
@@ -237,10 +254,31 @@ async function editProject(id) {
|
||||
document.getElementById("projectCategory").value = project.category || "";
|
||||
document.getElementById("projectActive").checked = project.isactive;
|
||||
|
||||
// Load images if available (imageurl field or parse from description)
|
||||
// Load images - check images array first, then fall back to imageurl
|
||||
portfolioImages = [];
|
||||
if (project.imageurl) {
|
||||
// If single image URL exists
|
||||
|
||||
// Try to parse images array
|
||||
if (project.images) {
|
||||
try {
|
||||
const imagesArr =
|
||||
typeof project.images === "string"
|
||||
? JSON.parse(project.images)
|
||||
: project.images;
|
||||
if (Array.isArray(imagesArr) && imagesArr.length > 0) {
|
||||
imagesArr.forEach((url) => {
|
||||
portfolioImages.push({
|
||||
url: url,
|
||||
filename: url.split("/").pop(),
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Failed to parse images:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to imageurl if no images array
|
||||
if (portfolioImages.length === 0 && project.imageurl) {
|
||||
portfolioImages.push({
|
||||
url: project.imageurl,
|
||||
filename: project.imageurl.split("/").pop(),
|
||||
@@ -286,6 +324,7 @@ async function saveProject() {
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
cache: "no-cache",
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
@@ -294,9 +333,15 @@ async function saveProject() {
|
||||
showSuccess(
|
||||
id
|
||||
? "Project updated successfully! 🎉"
|
||||
: "Project created successfully! 🎉"
|
||||
: "Project created successfully! 🎉",
|
||||
);
|
||||
projectModal.hide();
|
||||
// Immediately add to local data and re-render for instant feedback
|
||||
if (!id && data.project) {
|
||||
projectsData.unshift(data.project);
|
||||
renderProjects(projectsData);
|
||||
}
|
||||
// Also reload from server to ensure full sync
|
||||
loadProjects();
|
||||
} else {
|
||||
showError(data.message || "Failed to save project");
|
||||
@@ -308,23 +353,33 @@ async function saveProject() {
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
showDeleteConfirm(
|
||||
`Are you sure you want to delete "${name}"? This action cannot be undone.`,
|
||||
async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/portfolio/projects/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
cache: "no-cache",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess("Project deleted successfully");
|
||||
// Remove immediately from local data and re-render
|
||||
// Compare as strings to handle type mismatches
|
||||
const deletedId = String(id);
|
||||
projectsData = projectsData.filter((p) => String(p.id) !== deletedId);
|
||||
renderProjects(projectsData);
|
||||
} else {
|
||||
showError(data.message || "Failed to delete project");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete project:", error);
|
||||
showError("Failed to delete project");
|
||||
}
|
||||
},
|
||||
{ title: "Delete Project", confirmText: "Delete Project" },
|
||||
);
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
@@ -380,7 +435,7 @@ function renderPortfolioImages() {
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
@@ -395,100 +450,30 @@ function removePortfolioImage(index) {
|
||||
function openMediaLibrary(purpose) {
|
||||
currentMediaPicker = { purpose };
|
||||
|
||||
// Create backdrop
|
||||
const backdrop = document.createElement("div");
|
||||
backdrop.id = "mediaLibraryBackdrop";
|
||||
backdrop.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
// Initialize if not already
|
||||
initPortfolioMediaLibrary();
|
||||
|
||||
// Create modal
|
||||
const modal = document.createElement("div");
|
||||
modal.style.cssText = `
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
// Create close button
|
||||
const closeBtn = document.createElement("button");
|
||||
closeBtn.innerHTML = "×";
|
||||
closeBtn.style.cssText = `
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 10000;
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
closeBtn.onclick = closeMediaLibrary;
|
||||
|
||||
// Create iframe
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.id = "mediaLibraryFrame";
|
||||
iframe.src = "/admin/media-library.html?selectMode=true";
|
||||
iframe.style.cssText = `
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
`;
|
||||
|
||||
modal.appendChild(closeBtn);
|
||||
modal.appendChild(iframe);
|
||||
backdrop.appendChild(modal);
|
||||
document.body.appendChild(backdrop);
|
||||
|
||||
// Close on backdrop click
|
||||
backdrop.onclick = function (e) {
|
||||
if (e.target === backdrop) {
|
||||
closeMediaLibrary();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function closeMediaLibrary() {
|
||||
const backdrop = document.getElementById("mediaLibraryBackdrop");
|
||||
if (backdrop) {
|
||||
backdrop.remove();
|
||||
if (portfolioMediaLibrary) {
|
||||
portfolioMediaLibrary.open();
|
||||
}
|
||||
currentMediaPicker = null;
|
||||
}
|
||||
|
||||
function handleMediaSelection(media) {
|
||||
if (!currentMediaPicker) return;
|
||||
|
||||
if (currentMediaPicker.purpose === "portfolioImages") {
|
||||
// Handle multiple images
|
||||
// Handle multiple images - media can be array or single object
|
||||
const mediaArray = Array.isArray(media) ? media : [media];
|
||||
|
||||
// Add all selected images to portfolio images array
|
||||
mediaArray.forEach((item) => {
|
||||
// Check if image already exists
|
||||
if (!portfolioImages.find((img) => img.url === item.url)) {
|
||||
const itemUrl = item.path || item.url;
|
||||
if (!portfolioImages.find((img) => img.url === itemUrl)) {
|
||||
portfolioImages.push({
|
||||
url: item.url,
|
||||
filename: item.filename || item.url.split("/").pop(),
|
||||
url: itemUrl,
|
||||
filename:
|
||||
item.filename || item.originalName || itemUrl.split("/").pop(),
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -497,7 +482,7 @@ function handleMediaSelection(media) {
|
||||
showSuccess(`${mediaArray.length} image(s) added to portfolio gallery`);
|
||||
}
|
||||
|
||||
closeMediaLibrary();
|
||||
currentMediaPicker = null;
|
||||
}
|
||||
|
||||
// Toast Notification System
|
||||
|
||||
@@ -87,8 +87,10 @@ function initializeQuillEditor() {
|
||||
// Load all products
|
||||
async function loadProducts() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/products", {
|
||||
// Add cache-busting to ensure fresh data
|
||||
const response = await fetch(`/api/admin/products?_t=${Date.now()}`, {
|
||||
credentials: "include",
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
@@ -459,7 +461,7 @@ function renderImageVariants() {
|
||||
container.querySelectorAll('[data-action="remove"]').forEach((btn) => {
|
||||
btn.addEventListener("click", (e) => {
|
||||
const id = e.currentTarget.dataset.variantId;
|
||||
imageVariants = imageVariants.filter((v) => v.id !== id);
|
||||
imageVariants = imageVariants.filter((v) => String(v.id) !== String(id));
|
||||
renderImageVariants();
|
||||
});
|
||||
});
|
||||
@@ -469,7 +471,11 @@ function renderImageVariants() {
|
||||
item.addEventListener("click", (e) => {
|
||||
const variantId = e.currentTarget.dataset.variantId;
|
||||
const imageUrl = e.currentTarget.dataset.imageUrl;
|
||||
const variant = imageVariants.find((v) => v.id === variantId);
|
||||
const variant = imageVariants.find(
|
||||
(v) => String(v.id) === String(variantId)
|
||||
);
|
||||
|
||||
console.log("Image picker clicked:", { variantId, imageUrl, variant });
|
||||
|
||||
if (variant) {
|
||||
variant.image_url = imageUrl;
|
||||
@@ -480,16 +486,28 @@ function renderImageVariants() {
|
||||
.querySelectorAll(".image-picker-item")
|
||||
.forEach((i) => i.classList.remove("selected"));
|
||||
e.currentTarget.classList.add("selected");
|
||||
|
||||
console.log("Updated variant with new image:", variant);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add event listeners for input changes
|
||||
container.querySelectorAll("[data-variant-id]").forEach((input) => {
|
||||
input.addEventListener("input", (e) => {
|
||||
// Use 'input' for text/number fields, 'change' for radio/checkbox
|
||||
const eventType =
|
||||
input.type === "radio" || input.type === "checkbox" ? "change" : "input";
|
||||
input.addEventListener(eventType, (e) => {
|
||||
const id = e.target.dataset.variantId;
|
||||
const field = e.target.dataset.field;
|
||||
const variant = imageVariants.find((v) => v.id === id);
|
||||
const variant = imageVariants.find((v) => String(v.id) === String(id));
|
||||
|
||||
console.log("Input change:", {
|
||||
id,
|
||||
field,
|
||||
value: e.target.value,
|
||||
variant,
|
||||
});
|
||||
|
||||
if (variant) {
|
||||
if (field === "color_code_text") {
|
||||
@@ -517,6 +535,7 @@ function renderImageVariants() {
|
||||
} else {
|
||||
variant[field] = e.target.value;
|
||||
}
|
||||
console.log("Updated variant:", variant);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -564,7 +583,11 @@ async function editProduct(id) {
|
||||
product.isbestseller || false;
|
||||
|
||||
// Load image variants and extract unique product images
|
||||
imageVariants = product.images || [];
|
||||
// Convert numeric IDs to strings for consistency with newly created variants
|
||||
imageVariants = (product.images || []).map((img) => ({
|
||||
...img,
|
||||
id: String(img.id),
|
||||
}));
|
||||
console.log("Loaded image variants:", imageVariants);
|
||||
|
||||
// Build productImages array from unique image URLs in variants
|
||||
@@ -749,6 +772,9 @@ async function saveProduct() {
|
||||
: "✅ Product Created Successfully! Now visible on your shop page."
|
||||
);
|
||||
|
||||
// Notify frontend of product changes
|
||||
notifyFrontendChange("products");
|
||||
|
||||
// Wait a moment then close modal
|
||||
setTimeout(async () => {
|
||||
productModal.hide();
|
||||
@@ -783,141 +809,91 @@ async function saveProduct() {
|
||||
|
||||
// Delete product
|
||||
async function deleteProduct(id, name) {
|
||||
if (!confirm(`Are you sure you want to delete "${name}"?`)) {
|
||||
return;
|
||||
}
|
||||
showDeleteConfirm(
|
||||
`Are you sure you want to delete "${name}"? This action cannot be undone.`,
|
||||
async () => {
|
||||
try {
|
||||
// Immediately remove from UI for instant feedback
|
||||
const row = document
|
||||
.querySelector(`tr button[data-id="${id}"]`)
|
||||
?.closest("tr");
|
||||
if (row) {
|
||||
row.style.opacity = "0.5";
|
||||
row.style.pointerEvents = "none";
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/products/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
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");
|
||||
}
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Remove from local array immediately
|
||||
productsData = productsData.filter((p) => p.id !== id);
|
||||
// Re-render without waiting for server
|
||||
renderProducts(productsData);
|
||||
showSuccess("Product deleted successfully");
|
||||
// Also trigger frontend cache invalidation
|
||||
notifyFrontendChange("products");
|
||||
} else {
|
||||
// Restore row if delete failed
|
||||
if (row) {
|
||||
row.style.opacity = "1";
|
||||
row.style.pointerEvents = "auto";
|
||||
}
|
||||
showError(data.message || "Failed to delete product");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Delete error:", error);
|
||||
showError("Failed to delete product");
|
||||
// Reload to restore state
|
||||
loadProducts();
|
||||
}
|
||||
},
|
||||
{ title: "Delete Product", confirmText: "Delete Product" }
|
||||
);
|
||||
}
|
||||
|
||||
// ===== MEDIA LIBRARY INTEGRATION =====
|
||||
|
||||
// Listen for media selections from media library
|
||||
window.addEventListener("message", function (event) {
|
||||
// Security: verify origin if needed
|
||||
if (event.data.type === "mediaSelected" && currentMediaPicker) {
|
||||
handleMediaSelection(event.data.media);
|
||||
}
|
||||
});
|
||||
let productMediaLibrary = null;
|
||||
|
||||
function initProductMediaLibrary() {
|
||||
productMediaLibrary = new MediaLibrary({
|
||||
selectMode: true,
|
||||
multiple: true,
|
||||
onSelect: handleMediaSelection,
|
||||
});
|
||||
}
|
||||
|
||||
// Open media library modal
|
||||
function openMediaLibrary(purpose) {
|
||||
currentMediaPicker = { purpose }; // purpose: 'productImage' or 'variantImage'
|
||||
|
||||
// Create modal backdrop
|
||||
const backdrop = document.createElement("div");
|
||||
backdrop.id = "mediaLibraryBackdrop";
|
||||
backdrop.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.7);
|
||||
z-index: 9998;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
// Create modal container
|
||||
const modal = document.createElement("div");
|
||||
modal.id = "mediaLibraryModal";
|
||||
modal.style.cssText = `
|
||||
position: relative;
|
||||
width: 90%;
|
||||
max-width: 1200px;
|
||||
height: 85vh;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
|
||||
`;
|
||||
|
||||
// Create close button
|
||||
const closeBtn = document.createElement("button");
|
||||
closeBtn.innerHTML = '<i class="bi bi-x-lg"></i>';
|
||||
closeBtn.style.cssText = `
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
z-index: 10000;
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
closeBtn.onclick = closeMediaLibrary;
|
||||
|
||||
// Create iframe
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.id = "mediaLibraryFrame";
|
||||
iframe.src = "/admin/media-library.html?selectMode=true";
|
||||
iframe.style.cssText = `
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
`;
|
||||
|
||||
modal.appendChild(closeBtn);
|
||||
modal.appendChild(iframe);
|
||||
backdrop.appendChild(modal);
|
||||
document.body.appendChild(backdrop);
|
||||
|
||||
// Close on backdrop click
|
||||
backdrop.onclick = function (e) {
|
||||
if (e.target === backdrop) {
|
||||
closeMediaLibrary();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function closeMediaLibrary() {
|
||||
const backdrop = document.getElementById("mediaLibraryBackdrop");
|
||||
if (backdrop) {
|
||||
backdrop.remove();
|
||||
if (!productMediaLibrary) {
|
||||
initProductMediaLibrary();
|
||||
}
|
||||
currentMediaPicker = null;
|
||||
|
||||
productMediaLibrary.open();
|
||||
}
|
||||
|
||||
function handleMediaSelection(media) {
|
||||
if (!currentMediaPicker) return;
|
||||
|
||||
if (currentMediaPicker.purpose === "productImage") {
|
||||
// Handle multiple images
|
||||
// Handle multiple images - media is array in multi-select mode
|
||||
const mediaArray = Array.isArray(media) ? media : [media];
|
||||
|
||||
// Add all selected images to product images array
|
||||
mediaArray.forEach((item) => {
|
||||
// Check if image already exists
|
||||
if (!productImages.find((img) => img.url === item.url)) {
|
||||
// Check if image already exists - use path instead of url
|
||||
if (!productImages.find((img) => img.url === item.path)) {
|
||||
productImages.push({
|
||||
url: item.url,
|
||||
alt_text: item.filename || "",
|
||||
filename: item.filename,
|
||||
url: item.path,
|
||||
alt_text: item.name || "",
|
||||
filename: item.name,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -926,7 +902,7 @@ function handleMediaSelection(media) {
|
||||
showSuccess(`${mediaArray.length} image(s) added to product gallery`);
|
||||
}
|
||||
|
||||
closeMediaLibrary();
|
||||
currentMediaPicker = null;
|
||||
}
|
||||
|
||||
// ===== UTILITY FUNCTIONS =====
|
||||
|
||||
@@ -1,28 +1,41 @@
|
||||
// Settings Management JavaScript
|
||||
|
||||
let currentSettings = {};
|
||||
let mediaLibraryModal;
|
||||
let settingsMediaLibrary = null;
|
||||
let currentMediaTarget = null;
|
||||
let selectedMediaUrl = null;
|
||||
let allMedia = [];
|
||||
|
||||
// Initialize settings media library
|
||||
function initSettingsMediaLibrary() {
|
||||
if (typeof MediaLibrary !== "undefined" && !settingsMediaLibrary) {
|
||||
settingsMediaLibrary = new MediaLibrary({
|
||||
selectMode: true,
|
||||
multiple: false,
|
||||
onSelect: function (media) {
|
||||
if (!currentMediaTarget) return;
|
||||
|
||||
// Set the selected URL to the target field
|
||||
document.getElementById(currentMediaTarget).value = media.path;
|
||||
|
||||
// Update preview
|
||||
if (currentMediaTarget === "siteLogo") {
|
||||
document.getElementById(
|
||||
"logoPreview"
|
||||
).innerHTML = `<img src="${media.path}" alt="Logo" />`;
|
||||
} else if (currentMediaTarget === "siteFavicon") {
|
||||
document.getElementById(
|
||||
"faviconPreview"
|
||||
).innerHTML = `<img src="${media.path}" alt="Favicon" />`;
|
||||
}
|
||||
|
||||
showToast("Image selected successfully", "success");
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Initialize modal
|
||||
const modalElement = document.getElementById("mediaLibraryModal");
|
||||
if (modalElement) {
|
||||
mediaLibraryModal = new bootstrap.Modal(modalElement);
|
||||
}
|
||||
|
||||
// Setup media search
|
||||
const searchInput = document.getElementById("mediaSearch");
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener("input", filterMedia);
|
||||
}
|
||||
|
||||
const typeFilter = document.getElementById("mediaTypeFilter");
|
||||
if (typeFilter) {
|
||||
typeFilter.addEventListener("change", filterMedia);
|
||||
}
|
||||
// Initialize media library
|
||||
initSettingsMediaLibrary();
|
||||
|
||||
// Load saved theme
|
||||
loadTheme();
|
||||
@@ -251,153 +264,25 @@ function populateSettings() {
|
||||
}
|
||||
|
||||
// Media Library Functions - Make global for onclick handlers
|
||||
window.openMediaLibrary = async function (targetField) {
|
||||
window.openMediaLibrary = function (targetField) {
|
||||
console.log("openMediaLibrary called for:", targetField);
|
||||
currentMediaTarget = targetField;
|
||||
selectedMediaUrl = null;
|
||||
|
||||
// Load media files
|
||||
try {
|
||||
const response = await fetch("/api/admin/uploads", {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
allMedia = data.files || [];
|
||||
renderMediaGrid(allMedia);
|
||||
mediaLibraryModal.show();
|
||||
} else {
|
||||
showToast(data.message || "Failed to load media library", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load media library:", error);
|
||||
showToast("Failed to load media library. Please try again.", "error");
|
||||
// Initialize if not already
|
||||
initSettingsMediaLibrary();
|
||||
|
||||
if (settingsMediaLibrary) {
|
||||
settingsMediaLibrary.open();
|
||||
} else {
|
||||
showToast("Media library not available", "error");
|
||||
}
|
||||
};
|
||||
|
||||
function renderMediaGrid(media) {
|
||||
const grid = document.getElementById("mediaGrid");
|
||||
if (media.length === 0) {
|
||||
grid.innerHTML = `
|
||||
<div class="text-center py-5" style="grid-column: 1/-1;">
|
||||
<i class="bi bi-inbox fs-1 text-muted"></i>
|
||||
<p class="text-muted">No media files found</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
grid.innerHTML = media
|
||||
.map(
|
||||
(file) => `
|
||||
<div class="media-item" data-url="${
|
||||
file.path
|
||||
}" style="cursor: pointer; border: 3px solid transparent; border-radius: 8px; overflow: hidden; transition: all 0.3s;">
|
||||
${
|
||||
file.mimetype?.startsWith("image/")
|
||||
? `<img src="${file.path}" alt="${
|
||||
file.originalName || file.filename
|
||||
}" style="width: 100%; height: 150px; object-fit: cover;" />`
|
||||
: `<div style="width: 100%; height: 150px; background: #f8f9fa; display: flex; align-items: center; justify-content: center;">
|
||||
<i class="bi bi-file-earmark fs-1 text-muted"></i>
|
||||
</div>`
|
||||
}
|
||||
<div style="padding: 8px; font-size: 12px; text-align: center; background: white;">
|
||||
<div style="font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${
|
||||
file.originalName || file.filename
|
||||
}</div>
|
||||
<div style="color: #6c757d; font-size: 11px;">${formatFileSize(
|
||||
file.size
|
||||
)}</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
|
||||
// Add click listeners to all media items
|
||||
document.querySelectorAll(".media-item").forEach((item) => {
|
||||
item.addEventListener("click", function () {
|
||||
selectMedia(this.dataset.url);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function selectMedia(url) {
|
||||
// Remove previous selection
|
||||
document.querySelectorAll(".media-item").forEach((el) => {
|
||||
el.style.border = "3px solid transparent";
|
||||
});
|
||||
|
||||
// Mark current selection - find the clicked item
|
||||
document.querySelectorAll(".media-item").forEach((el) => {
|
||||
if (el.dataset.url === url) {
|
||||
el.style.border = "3px solid #667eea";
|
||||
el.style.background = "#f8f9fa";
|
||||
}
|
||||
});
|
||||
|
||||
selectedMediaUrl = url;
|
||||
}
|
||||
|
||||
window.selectMediaFile = function () {
|
||||
if (!selectedMediaUrl) {
|
||||
window.showToast("Please select a media file", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the selected URL to the target field
|
||||
document.getElementById(currentMediaTarget).value = selectedMediaUrl;
|
||||
|
||||
// Update preview
|
||||
if (currentMediaTarget === "siteLogo") {
|
||||
document.getElementById(
|
||||
"logoPreview"
|
||||
).innerHTML = `<img src="${selectedMediaUrl}" alt="Logo" />`;
|
||||
} else if (currentMediaTarget === "siteFavicon") {
|
||||
document.getElementById(
|
||||
"faviconPreview"
|
||||
).innerHTML = `<img src="${selectedMediaUrl}" alt="Favicon" />`;
|
||||
}
|
||||
|
||||
// Close modal
|
||||
mediaLibraryModal.hide();
|
||||
window.showToast("Image selected successfully", "success");
|
||||
// This is now handled by the MediaLibrary component's onSelect callback
|
||||
showToast("Please click on an image to select it", "info");
|
||||
};
|
||||
|
||||
function filterMedia() {
|
||||
const searchTerm = document.getElementById("mediaSearch").value.toLowerCase();
|
||||
const typeFilter = document.getElementById("mediaTypeFilter").value;
|
||||
|
||||
let filtered = allMedia;
|
||||
|
||||
// Filter by search term
|
||||
if (searchTerm) {
|
||||
filtered = filtered.filter(
|
||||
(file) =>
|
||||
file.filename.toLowerCase().includes(searchTerm) ||
|
||||
file.originalName?.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
}
|
||||
|
||||
// Filter by type
|
||||
if (typeFilter !== "all") {
|
||||
filtered = filtered.filter((file) => {
|
||||
if (typeFilter === "image") return file.mimetype?.startsWith("image/");
|
||||
if (typeFilter === "video") return file.mimetype?.startsWith("video/");
|
||||
if (typeFilter === "document")
|
||||
return (
|
||||
file.mimetype?.includes("pdf") ||
|
||||
file.mimetype?.includes("document") ||
|
||||
file.mimetype?.includes("text")
|
||||
);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
renderMediaGrid(filtered);
|
||||
}
|
||||
|
||||
function formatFileSize(bytes) {
|
||||
if (!bytes) return "0 B";
|
||||
const k = 1024;
|
||||
|
||||
@@ -12,6 +12,7 @@ const rolePermissions = {
|
||||
"View Reports",
|
||||
"View Financial Data",
|
||||
],
|
||||
Sales: ["Manage Products", "Manage Orders", "View Reports"],
|
||||
Admin: [
|
||||
"Manage Products",
|
||||
"Manage Portfolio",
|
||||
@@ -19,14 +20,8 @@ const rolePermissions = {
|
||||
"Manage Pages",
|
||||
"Manage Users",
|
||||
"View Reports",
|
||||
],
|
||||
MasterAdmin: [
|
||||
"Full System Access",
|
||||
"Manage Settings",
|
||||
"Manage Users",
|
||||
"Manage All Content",
|
||||
"View Logs",
|
||||
"System Configuration",
|
||||
],
|
||||
};
|
||||
|
||||
@@ -85,22 +80,22 @@ function renderUsers(users) {
|
||||
<td>${formatDate(u.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editUser('${escapeHtml(
|
||||
u.id
|
||||
u.id,
|
||||
)}')" title="Edit User">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-warning" onclick="showChangePassword('${escapeHtml(
|
||||
u.id
|
||||
u.id,
|
||||
)}', '${escapeHtml(u.name)}')" title="Change Password">
|
||||
<i class="bi bi-key"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteUser('${escapeHtml(
|
||||
u.id
|
||||
u.id,
|
||||
)}', '${escapeHtml(u.name)}')" title="Delete User">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>`
|
||||
</tr>`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
@@ -111,7 +106,7 @@ function filterUsers() {
|
||||
(u) =>
|
||||
u.name.toLowerCase().includes(searchTerm) ||
|
||||
u.email.toLowerCase().includes(searchTerm) ||
|
||||
u.username.toLowerCase().includes(searchTerm)
|
||||
u.username.toLowerCase().includes(searchTerm),
|
||||
);
|
||||
renderUsers(filtered);
|
||||
}
|
||||
@@ -174,6 +169,18 @@ async function saveUser() {
|
||||
showError("Password must be at least 8 characters long");
|
||||
return;
|
||||
}
|
||||
if (!/[A-Z]/.test(password)) {
|
||||
showError("Password must contain at least one uppercase letter");
|
||||
return;
|
||||
}
|
||||
if (!/[a-z]/.test(password)) {
|
||||
showError("Password must contain at least one lowercase letter");
|
||||
return;
|
||||
}
|
||||
if (!/[0-9]/.test(password)) {
|
||||
showError("Password must contain at least one number");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const formData = {
|
||||
@@ -212,7 +219,7 @@ async function saveUser() {
|
||||
|
||||
if (data.success) {
|
||||
showSuccess(
|
||||
id ? "User updated successfully" : "User created successfully"
|
||||
id ? "User updated successfully" : "User created successfully",
|
||||
);
|
||||
userModal.hide();
|
||||
loadUsers();
|
||||
@@ -254,6 +261,21 @@ async function changePassword() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/[A-Z]/.test(newPassword)) {
|
||||
showError("Password must contain at least one uppercase letter");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/[a-z]/.test(newPassword)) {
|
||||
showError("Password must contain at least one lowercase letter");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/[0-9]/.test(newPassword)) {
|
||||
showError("Password must contain at least one number");
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading("Changing password...");
|
||||
|
||||
try {
|
||||
@@ -281,34 +303,33 @@ async function changePassword() {
|
||||
}
|
||||
|
||||
async function deleteUser(id, name) {
|
||||
if (
|
||||
!confirm(
|
||||
`Are you sure you want to delete user "${name}"? This action cannot be undone.`
|
||||
)
|
||||
)
|
||||
return;
|
||||
showDeleteConfirm(
|
||||
`Are you sure you want to delete user "${name}"? This action cannot be undone.`,
|
||||
async () => {
|
||||
showLoading("Deleting user...");
|
||||
|
||||
showLoading("Deleting user...");
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
hideLoading();
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
hideLoading();
|
||||
|
||||
if (data.success) {
|
||||
showSuccess("User deleted successfully");
|
||||
loadUsers();
|
||||
} else {
|
||||
showError(data.message || "Failed to delete user");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete user:", error);
|
||||
hideLoading();
|
||||
showError("Failed to delete user");
|
||||
}
|
||||
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);
|
||||
hideLoading();
|
||||
showError("Failed to delete user");
|
||||
}
|
||||
},
|
||||
{ title: "Delete User", confirmText: "Delete User" },
|
||||
);
|
||||
}
|
||||
|
||||
function updatePermissionsPreview() {
|
||||
@@ -323,7 +344,7 @@ function updatePermissionsPreview() {
|
||||
<i class="bi bi-check-circle-fill" style="color: #10b981; margin-right: 8px;"></i>
|
||||
<span>${perm}</span>
|
||||
</div>
|
||||
`
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
1709
website/admin/media-library-old.html
Normal file
1709
website/admin/media-library-old.html
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
411
website/admin/menu-old.html
Normal file
411
website/admin/menu-old.html
Normal file
@@ -0,0 +1,411 @@
|
||||
<!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"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio"><i class="bi bi-easel"></i> Portfolio</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu" class="active"
|
||||
><i class="bi bi-list"></i> Menu</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/customers"
|
||||
><i class="bi bi-person-hearts"></i> Customers</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");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="/admin/js/auth.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
692
website/admin/pages-old.html
Normal file
692
website/admin/pages-old.html
Normal file
@@ -0,0 +1,692 @@
|
||||
<!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"
|
||||
/>
|
||||
<!-- Quill Editor CSS -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/quill@1.3.7/dist/quill.snow.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="/admin/css/admin-style.css" />
|
||||
<link rel="stylesheet" href="/admin/css/media-library.css" />
|
||||
<style>
|
||||
/* Quill Editor Styling */
|
||||
.ql-container {
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
}
|
||||
.ql-editor {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Quill Editor Scrollbar */
|
||||
.ql-editor::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.ql-editor::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.ql-editor::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.ql-editor::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
/* Modal Enhancements */
|
||||
#pageModal .modal-dialog {
|
||||
max-width: 90vw;
|
||||
margin: 1.75rem auto;
|
||||
}
|
||||
|
||||
#pageModal .modal-content {
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#pageModal .modal-header {
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#pageModal .modal-body {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
flex: 1 1 auto;
|
||||
max-height: calc(90vh - 140px);
|
||||
}
|
||||
|
||||
#pageModal .modal-footer {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Scrollbar Styling */
|
||||
#pageModal .modal-body::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
#pageModal .modal-body::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#pageModal .modal-body::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#pageModal .modal-body::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
/* Contact Fields - removed duplicate overflow styles */
|
||||
|
||||
/* Resize Handle */
|
||||
.modal-resize-handle {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: se-resize;
|
||||
background: linear-gradient(135deg, transparent 50%, #6c757d 50%);
|
||||
opacity: 0.5;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.modal-resize-handle:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Fullscreen Toggle Button */
|
||||
.btn-fullscreen {
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
top: 12px;
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 1.2rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #6c757d;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-fullscreen:hover {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* Fullscreen Mode */
|
||||
.modal-fullscreen .modal-dialog {
|
||||
max-width: 100vw;
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.modal-fullscreen .modal-content {
|
||||
max-height: 100vh;
|
||||
height: 100vh;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.modal-fullscreen .modal-body {
|
||||
max-height: calc(100vh - 140px);
|
||||
}
|
||||
|
||||
/* Editor resize styling */
|
||||
.editor-resizable {
|
||||
position: relative;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 4px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.editor-resize-handle {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: nwse-resize;
|
||||
background: linear-gradient(135deg, transparent 50%, #667eea 50%);
|
||||
z-index: 1000;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.editor-resize-handle:hover {
|
||||
background: linear-gradient(135deg, transparent 50%, #5568d3 50%);
|
||||
}
|
||||
|
||||
.editor-resize-handle:active {
|
||||
background: linear-gradient(135deg, transparent 50%, #4451b8 50%);
|
||||
}
|
||||
|
||||
/* Expanded state removed - not needed */
|
||||
|
||||
/* Team Member Card in Admin */
|
||||
.team-member-card {
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.team-member-card:hover {
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
|
||||
}
|
||||
|
||||
.team-member-preview {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
border: 3px solid #667eea;
|
||||
background: #f8f9fa;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 15px;
|
||||
}
|
||||
|
||||
.team-member-preview img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.team-member-preview i {
|
||||
font-size: 2rem;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.team-member-handle {
|
||||
cursor: move;
|
||||
color: #cbd5e0;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.team-member-handle:hover {
|
||||
color: #667eea;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio"><i class="bi bi-easel"></i> Portfolio</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages" class="active"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/customers"
|
||||
><i class="bi bi-person-hearts"></i> Customers</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
|
||||
>
|
||||
|
||||
<!-- Structured Contact Fields (shown only for contact page) -->
|
||||
<div
|
||||
id="contactStructuredFields"
|
||||
style="display: none"
|
||||
class="editor-resizable"
|
||||
>
|
||||
<div
|
||||
id="contactFieldsContent"
|
||||
style="
|
||||
height: 500px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 15px;
|
||||
"
|
||||
>
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
<strong>Contact Page:</strong> Edit each section
|
||||
independently. The layout will remain organized.
|
||||
</div>
|
||||
|
||||
<!-- Header Section -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<i class="bi bi-card-heading"></i> Header Section
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="contactHeaderTitle"
|
||||
placeholder="Our Contact Information"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Subtitle</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="contactHeaderSubtitle"
|
||||
placeholder="Reach out to us through any of these channels"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contact Information -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header bg-success text-white">
|
||||
<i class="bi bi-telephone"></i> Contact Information
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Phone Number</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="contactPhone"
|
||||
placeholder="+1 (555) 123-4567"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Email Address</label>
|
||||
<input
|
||||
type="email"
|
||||
class="form-control"
|
||||
id="contactEmail"
|
||||
placeholder="contact@skyartshop.com"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Physical Address</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="contactAddress"
|
||||
placeholder="123 Art Street, Creative City, CC 12345"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Business Hours -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<i class="bi bi-clock"></i> Business Hours
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="businessHoursList">
|
||||
<!-- Dynamic business hours will be added here -->
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-primary"
|
||||
onclick="addBusinessHour()"
|
||||
>
|
||||
<i class="bi bi-plus-circle"></i> Add Time Slot
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="editor-resize-handle"
|
||||
data-target="contactFieldsContent"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- About Page with Team Members Section -->
|
||||
<div id="aboutWithTeamFields" style="display: none">
|
||||
<div class="alert alert-info mb-3">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
<strong>About Page:</strong> Edit the main content and
|
||||
manage your team members below.
|
||||
</div>
|
||||
|
||||
<!-- About Content Editor -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<i class="bi bi-file-text"></i> About Content
|
||||
</div>
|
||||
<div class="card-body p-0 position-relative">
|
||||
<div class="editor-resizable">
|
||||
<div
|
||||
id="aboutContentEditor"
|
||||
style="background: white; height: 300px"
|
||||
></div>
|
||||
<div
|
||||
class="editor-resize-handle"
|
||||
data-target="aboutContentEditor"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Team Members Section -->
|
||||
<div class="card mb-3">
|
||||
<div
|
||||
class="card-header bg-success text-white d-flex justify-content-between align-items-center"
|
||||
>
|
||||
<span><i class="bi bi-people"></i> Team Members</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-light"
|
||||
onclick="addTeamMember()"
|
||||
>
|
||||
<i class="bi bi-plus-lg"></i> Add Member
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="teamMembersList" class="row g-3">
|
||||
<div class="col-12 text-center text-muted py-3">
|
||||
<i class="bi bi-people" style="font-size: 3rem"></i>
|
||||
<p class="mt-2">
|
||||
No team members yet. Click "Add Member" to get
|
||||
started.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Regular Quill Editor (for other pages) -->
|
||||
<div id="regularContentEditor" class="editor-resizable">
|
||||
<div
|
||||
id="pageContentEditor"
|
||||
style="background: white; height: 400px"
|
||||
></div>
|
||||
<div
|
||||
class="editor-resize-handle"
|
||||
data-target="pageContentEditor"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
class="form-control d-none"
|
||||
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 class="modal-resize-handle" title="Drag to resize"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notification Modal -->
|
||||
<div class="modal fade" id="notificationModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content" id="notificationModalContent">
|
||||
<div class="modal-header" id="notificationModalHeader">
|
||||
<h5 class="modal-title" id="notificationModalTitle">
|
||||
<i class="bi" id="notificationModalIcon"></i>
|
||||
<span id="notificationModalTitleText"></span>
|
||||
</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body" id="notificationModalBody">
|
||||
<!-- Message will be inserted here -->
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Confirmation Modal -->
|
||||
<div class="modal fade" id="confirmModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-warning" style="border-width: 3px">
|
||||
<div class="modal-header bg-warning text-dark">
|
||||
<h5 class="modal-title">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
||||
Confirm Action
|
||||
</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body" id="confirmModalBody">
|
||||
<!-- Confirmation message will be inserted here -->
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-danger"
|
||||
id="confirmModalButton"
|
||||
>
|
||||
<i class="bi bi-trash"></i> Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quill Editor JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@1.3.7/dist/quill.min.js"></script>
|
||||
<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/media-library.js"></script>
|
||||
<script src="/admin/js/pages.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -18,10 +18,11 @@
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="stylesheet" href="/admin/css/admin-style.css" />
|
||||
<link rel="stylesheet" href="/admin/css/media-library.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<div class="sidebar-brand">Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard"
|
||||
@@ -63,6 +64,11 @@
|
||||
<li>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/customers"
|
||||
><i class="bi bi-person-hearts"></i> Customers</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -262,6 +268,8 @@
|
||||
<!-- Quill Editor JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@1.3.7/dist/quill.js"></script>
|
||||
<script src="/admin/js/auth.js"></script>
|
||||
<script src="/admin/js/admin-utils.js"></script>
|
||||
<script src="/admin/js/media-library.js"></script>
|
||||
<script src="/admin/js/portfolio.js?v=5.0"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -18,11 +18,12 @@
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="stylesheet" href="/admin/css/admin-style.css" />
|
||||
<link rel="stylesheet" href="/admin/css/media-library.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar" id="sidebar">
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<div class="sidebar-brand">Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard"
|
||||
@@ -40,9 +41,7 @@
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
<a href="/admin/portfolio"><i class="bi bi-easel"></i> Portfolio</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
@@ -66,6 +65,11 @@
|
||||
<li>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/customers"
|
||||
><i class="bi bi-person-hearts"></i> Customers</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -385,6 +389,8 @@
|
||||
<!-- Quill Editor JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@1.3.7/dist/quill.js"></script>
|
||||
<script src="/admin/js/auth.js"></script>
|
||||
<script src="/admin/js/products.js"></script>
|
||||
<script src="/admin/js/admin-utils.js?v=20260115c"></script>
|
||||
<script src="/admin/js/media-library.js"></script>
|
||||
<script src="/admin/js/products.js?v=20260115c"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
640
website/admin/settings-old.html
Normal file
640
website/admin/settings-old.html
Normal file
@@ -0,0 +1,640 @@
|
||||
<!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" />
|
||||
<link rel="stylesheet" href="/admin/css/media-library.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>
|
||||
<!-- Toast Notification Container -->
|
||||
<div class="toast-container" id="toastContainer"></div>
|
||||
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard"
|
||||
><i class="bi bi-speedometer2"></i> Dashboard</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/homepage"
|
||||
><i class="bi bi-house"></i> Homepage Editor</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio"><i class="bi bi-easel"></i> Portfolio</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/pages"
|
||||
><i class="bi bi-file-text"></i> Custom Pages</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/media-library"
|
||||
><i class="bi bi-images"></i> Media Library</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/menu"><i class="bi bi-list"></i> Menu</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings" class="active"
|
||||
><i class="bi bi-gear"></i> Settings</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/customers"
|
||||
><i class="bi bi-person-hearts"></i> Customers</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>
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="siteLogo"
|
||||
placeholder="Select logo from media library"
|
||||
readonly
|
||||
/>
|
||||
<button
|
||||
class="btn btn-outline-secondary"
|
||||
type="button"
|
||||
onclick="openMediaLibrary('siteLogo')"
|
||||
>
|
||||
<i class="bi bi-images"></i> Choose from Library
|
||||
</button>
|
||||
</div>
|
||||
<div class="logo-preview" id="logoPreview">
|
||||
<span class="text-muted">No logo selected</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="siteFavicon" class="form-label">Favicon</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="siteFavicon"
|
||||
placeholder="Select favicon from media library"
|
||||
readonly
|
||||
/>
|
||||
<button
|
||||
class="btn btn-outline-secondary"
|
||||
type="button"
|
||||
onclick="openMediaLibrary('siteFavicon')"
|
||||
>
|
||||
<i class="bi bi-images"></i> Choose from Library
|
||||
</button>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<!-- Media Library Modal -->
|
||||
<div
|
||||
class="modal fade"
|
||||
id="mediaLibraryModal"
|
||||
tabindex="-1"
|
||||
aria-labelledby="mediaLibraryModalLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="mediaLibraryModalLabel">
|
||||
<i class="bi bi-images"></i> Select from Media Library
|
||||
</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="mediaSearch"
|
||||
placeholder="Search media files..."
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<select class="form-select" id="mediaTypeFilter">
|
||||
<option value="all">All Types</option>
|
||||
<option value="image">Images</option>
|
||||
<option value="video">Videos</option>
|
||||
<option value="document">Documents</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="mediaGrid"
|
||||
style="
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 15px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
"
|
||||
>
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-hourglass-split fs-1 text-muted"></i>
|
||||
<p class="text-muted">Loading media...</p>
|
||||
</div>
|
||||
</div>
|
||||
</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="selectMediaFile()"
|
||||
>
|
||||
<i class="bi bi-check-lg"></i> Select
|
||||
</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/media-library.js"></script>
|
||||
<script src="/admin/js/settings.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -78,7 +78,9 @@
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
background: white;
|
||||
border-left: 4px solid;
|
||||
animation: slideIn 0.3s ease-out, fadeOut 0.3s ease-in 2.7s;
|
||||
animation:
|
||||
slideIn 0.3s ease-out,
|
||||
fadeOut 0.3s ease-in 2.7s;
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
min-width: 320px;
|
||||
@@ -232,7 +234,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
||||
<div class="sidebar-brand">Sky Art Shop</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a href="/admin/dashboard"
|
||||
@@ -248,9 +250,7 @@
|
||||
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/portfolio"
|
||||
><i class="bi bi-easel"></i> Portfolio</a
|
||||
>
|
||||
<a href="/admin/portfolio"><i class="bi bi-easel"></i> Portfolio</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
||||
@@ -276,6 +276,11 @@
|
||||
><i class="bi bi-people"></i> Users</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/customers"
|
||||
><i class="bi bi-person-hearts"></i> Customers</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -397,7 +402,8 @@
|
||||
id="userPassword"
|
||||
/>
|
||||
<small class="text-muted"
|
||||
>Leave blank to keep current password (when editing)</small
|
||||
>Min 8 chars, uppercase, lowercase, number. Leave blank when
|
||||
editing to keep current.</small
|
||||
>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
@@ -421,8 +427,8 @@
|
||||
>
|
||||
<option value="Cashier">Cashier</option>
|
||||
<option value="Accountant">Accountant</option>
|
||||
<option value="Sales">Sales</option>
|
||||
<option value="Admin">Admin</option>
|
||||
<option value="MasterAdmin">Master Admin</option>
|
||||
</select>
|
||||
<small class="text-muted"
|
||||
>Role determines access permissions</small
|
||||
@@ -516,7 +522,10 @@
|
||||
id="newPassword"
|
||||
required
|
||||
/>
|
||||
<small class="text-muted">Minimum 8 characters</small>
|
||||
<small class="text-muted"
|
||||
>Min 8 chars, must include uppercase, lowercase, and
|
||||
number</small
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
@@ -557,6 +566,7 @@
|
||||
|
||||
<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/admin-utils.js"></script>
|
||||
<script src="/admin/js/users.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
1162
website/assets/css/mobile-fixes.css
Normal file
1162
website/assets/css/mobile-fixes.css
Normal file
File diff suppressed because it is too large
Load Diff
2499
website/assets/css/modern-theme.css
Normal file
2499
website/assets/css/modern-theme.css
Normal file
File diff suppressed because it is too large
Load Diff
870
website/assets/js/modern-theme.js
Normal file
870
website/assets/js/modern-theme.js
Normal file
@@ -0,0 +1,870 @@
|
||||
/* ============================================
|
||||
SKY ART SHOP - MODERN THEME JAVASCRIPT
|
||||
Complete Frontend Functionality
|
||||
============================================ */
|
||||
|
||||
// Global State
|
||||
const SkyArtShop = {
|
||||
cart: JSON.parse(localStorage.getItem("skyart_cart") || "[]"),
|
||||
wishlist: JSON.parse(localStorage.getItem("skyart_wishlist") || "[]"),
|
||||
|
||||
// Initialize
|
||||
init() {
|
||||
this.initNavbar();
|
||||
// Delay slider init slightly to ensure DOM is ready
|
||||
requestAnimationFrame(() => {
|
||||
this.initSlider();
|
||||
});
|
||||
this.initCart();
|
||||
this.initWishlist();
|
||||
this.initWishlistDrawer();
|
||||
this.initProducts();
|
||||
this.initAnimations();
|
||||
this.updateCartCount();
|
||||
this.updateWishlistCount();
|
||||
},
|
||||
|
||||
// Navbar Functionality
|
||||
initNavbar() {
|
||||
const navbar = document.querySelector(".nav-wrapper");
|
||||
const mobileToggle = document.querySelector(".nav-mobile-toggle");
|
||||
const navMenu = document.querySelector(".nav-menu");
|
||||
|
||||
// Scroll effect
|
||||
if (navbar) {
|
||||
window.addEventListener("scroll", () => {
|
||||
if (window.scrollY > 50) {
|
||||
navbar.classList.add("scrolled");
|
||||
} else {
|
||||
navbar.classList.remove("scrolled");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Mobile menu toggle
|
||||
if (mobileToggle && navMenu) {
|
||||
mobileToggle.addEventListener("click", () => {
|
||||
navMenu.classList.toggle("open");
|
||||
mobileToggle.classList.toggle("active");
|
||||
});
|
||||
|
||||
// Close menu when clicking a link
|
||||
navMenu.querySelectorAll(".nav-link").forEach((link) => {
|
||||
link.addEventListener("click", () => {
|
||||
navMenu.classList.remove("open");
|
||||
mobileToggle.classList.remove("active");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Set active nav link
|
||||
const currentPath = window.location.pathname;
|
||||
document.querySelectorAll(".nav-link").forEach((link) => {
|
||||
if (link.getAttribute("href") === currentPath) {
|
||||
link.classList.add("active");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Hero Slider
|
||||
sliderInitialized: false,
|
||||
sliderInterval: null,
|
||||
|
||||
initSlider() {
|
||||
const slider = document.querySelector(".hero-slider");
|
||||
if (!slider) return;
|
||||
|
||||
// Prevent multiple initializations
|
||||
if (this.sliderInitialized || slider.dataset.initialized === "true") {
|
||||
return;
|
||||
}
|
||||
this.sliderInitialized = true;
|
||||
slider.dataset.initialized = "true";
|
||||
|
||||
const slides = slider.querySelectorAll(".slide");
|
||||
const dots = slider.querySelectorAll(".slider-dot");
|
||||
const prevBtn = slider.querySelector(".slider-arrow.prev");
|
||||
const nextBtn = slider.querySelector(".slider-arrow.next");
|
||||
|
||||
// Need at least 2 slides for auto-play to make sense
|
||||
if (slides.length < 2) return;
|
||||
|
||||
let currentSlide = 0;
|
||||
let isAnimating = false;
|
||||
const self = this;
|
||||
|
||||
// Clear any existing interval
|
||||
if (this.sliderInterval) {
|
||||
clearInterval(this.sliderInterval);
|
||||
this.sliderInterval = null;
|
||||
}
|
||||
|
||||
// Initialize slides - first slide active, others positioned off-screen right
|
||||
slides.forEach((slide, i) => {
|
||||
slide.classList.remove("active", "outgoing");
|
||||
slide.style.transition = "none";
|
||||
if (i === 0) {
|
||||
slide.classList.add("active");
|
||||
}
|
||||
});
|
||||
// Force reflow then re-enable transitions
|
||||
void slider.offsetWidth;
|
||||
slides.forEach((slide) => (slide.style.transition = ""));
|
||||
if (dots[0]) dots[0].classList.add("active");
|
||||
|
||||
const showSlide = (index) => {
|
||||
if (isAnimating) return;
|
||||
|
||||
const prevIndex = currentSlide;
|
||||
currentSlide = (index + slides.length) % slides.length;
|
||||
|
||||
if (prevIndex === currentSlide) return;
|
||||
|
||||
isAnimating = true;
|
||||
|
||||
const oldSlide = slides[prevIndex];
|
||||
const newSlide = slides[currentSlide];
|
||||
|
||||
// Update dots
|
||||
dots.forEach((dot, i) =>
|
||||
dot.classList.toggle("active", i === currentSlide),
|
||||
);
|
||||
|
||||
// Position new slide off-screen to the right (no transition)
|
||||
newSlide.style.transition = "none";
|
||||
newSlide.classList.remove("outgoing");
|
||||
newSlide.style.transform = "translateX(100%)";
|
||||
|
||||
// Force browser to register the position
|
||||
void newSlide.offsetWidth;
|
||||
|
||||
// Re-enable transition and animate
|
||||
newSlide.style.transition = "";
|
||||
newSlide.style.transform = "";
|
||||
newSlide.classList.add("active");
|
||||
|
||||
// Old slide moves out to the left
|
||||
oldSlide.classList.remove("active");
|
||||
oldSlide.classList.add("outgoing");
|
||||
|
||||
// Cleanup after animation (800ms matches CSS)
|
||||
setTimeout(() => {
|
||||
oldSlide.classList.remove("outgoing");
|
||||
oldSlide.style.transform = "";
|
||||
isAnimating = false;
|
||||
}, 850);
|
||||
};
|
||||
|
||||
const nextSlide = () => showSlide(currentSlide + 1);
|
||||
const prevSlide = () => showSlide(currentSlide - 1);
|
||||
|
||||
// Auto-play with 7 second intervals (7000ms)
|
||||
const startAutoPlay = () => {
|
||||
// Clear any existing interval first
|
||||
if (self.sliderInterval) {
|
||||
clearInterval(self.sliderInterval);
|
||||
}
|
||||
self.sliderInterval = setInterval(nextSlide, 7000);
|
||||
};
|
||||
|
||||
const stopAutoPlay = () => {
|
||||
if (self.sliderInterval) {
|
||||
clearInterval(self.sliderInterval);
|
||||
self.sliderInterval = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Event listeners
|
||||
if (prevBtn)
|
||||
prevBtn.addEventListener("click", () => {
|
||||
stopAutoPlay();
|
||||
prevSlide();
|
||||
startAutoPlay();
|
||||
});
|
||||
if (nextBtn)
|
||||
nextBtn.addEventListener("click", () => {
|
||||
stopAutoPlay();
|
||||
nextSlide();
|
||||
startAutoPlay();
|
||||
});
|
||||
|
||||
dots.forEach((dot, i) => {
|
||||
dot.addEventListener("click", () => {
|
||||
stopAutoPlay();
|
||||
showSlide(i);
|
||||
startAutoPlay();
|
||||
});
|
||||
});
|
||||
|
||||
// Start auto-play immediately (first slide already initialized)
|
||||
startAutoPlay();
|
||||
|
||||
// Pause on hover
|
||||
slider.addEventListener("mouseenter", stopAutoPlay);
|
||||
slider.addEventListener("mouseleave", startAutoPlay);
|
||||
|
||||
// Pause when tab is not visible, resume when visible
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (document.hidden) {
|
||||
stopAutoPlay();
|
||||
} else {
|
||||
startAutoPlay();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Cart Functionality
|
||||
initCart() {
|
||||
const cartBtn = document.querySelector(".cart-btn");
|
||||
const cartDrawer = document.querySelector(".cart-drawer");
|
||||
const cartOverlay = document.querySelector(".cart-overlay");
|
||||
const cartClose = document.querySelector(".cart-close");
|
||||
|
||||
if (cartBtn && cartDrawer) {
|
||||
cartBtn.addEventListener("click", () => this.openCart());
|
||||
}
|
||||
|
||||
if (cartClose) cartClose.addEventListener("click", () => this.closeCart());
|
||||
if (cartOverlay)
|
||||
cartOverlay.addEventListener("click", () => this.closeCart());
|
||||
|
||||
// Close on escape
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") this.closeCart();
|
||||
});
|
||||
},
|
||||
|
||||
openCart() {
|
||||
const cartDrawer = document.querySelector(".cart-drawer");
|
||||
const cartOverlay = document.querySelector(".cart-overlay");
|
||||
if (cartDrawer) cartDrawer.classList.add("open");
|
||||
if (cartOverlay) cartOverlay.classList.add("open");
|
||||
document.body.style.overflow = "hidden";
|
||||
this.renderCart();
|
||||
},
|
||||
|
||||
closeCart() {
|
||||
const cartDrawer = document.querySelector(".cart-drawer");
|
||||
const cartOverlay = document.querySelector(".cart-overlay");
|
||||
if (cartDrawer) cartDrawer.classList.remove("open");
|
||||
if (cartOverlay) cartOverlay.classList.remove("open");
|
||||
document.body.style.overflow = "";
|
||||
},
|
||||
|
||||
addToCart(product) {
|
||||
const existingItem = this.cart.find((item) => item.id === product.id);
|
||||
|
||||
if (existingItem) {
|
||||
existingItem.quantity += 1;
|
||||
} else {
|
||||
this.cart.push({ ...product, quantity: 1 });
|
||||
}
|
||||
|
||||
this.saveCart();
|
||||
this.updateCartCount();
|
||||
this.showNotification(`${product.name} added to cart!`);
|
||||
this.openCart();
|
||||
},
|
||||
|
||||
removeFromCart(productId) {
|
||||
this.cart = this.cart.filter((item) => item.id !== productId);
|
||||
this.saveCart();
|
||||
this.updateCartCount();
|
||||
this.renderCart();
|
||||
},
|
||||
|
||||
updateCartQty(productId, change) {
|
||||
const item = this.cart.find((item) => item.id === productId);
|
||||
if (item) {
|
||||
item.quantity += change;
|
||||
if (item.quantity <= 0) {
|
||||
this.removeFromCart(productId);
|
||||
} else {
|
||||
this.saveCart();
|
||||
this.renderCart();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
saveCart() {
|
||||
localStorage.setItem("skyart_cart", JSON.stringify(this.cart));
|
||||
},
|
||||
|
||||
updateCartCount() {
|
||||
const count = this.cart.reduce((sum, item) => sum + item.quantity, 0);
|
||||
document.querySelectorAll(".cart-count").forEach((el) => {
|
||||
el.textContent = count;
|
||||
el.style.display = count > 0 ? "flex" : "none";
|
||||
});
|
||||
},
|
||||
|
||||
getCartTotal() {
|
||||
return this.cart.reduce(
|
||||
(sum, item) => sum + parseFloat(item.price) * item.quantity,
|
||||
0,
|
||||
);
|
||||
},
|
||||
|
||||
renderCart() {
|
||||
const cartItems = document.querySelector(".cart-items");
|
||||
const cartTotal = document.querySelector(".cart-total-amount");
|
||||
|
||||
if (!cartItems) return;
|
||||
|
||||
if (this.cart.length === 0) {
|
||||
cartItems.innerHTML = `
|
||||
<div class="cart-empty">
|
||||
<i class="bi bi-cart-x" style="font-size: 3rem; color: var(--text-light); margin-bottom: 16px;"></i>
|
||||
<p>Your cart is empty</p>
|
||||
<a href="/shop" class="btn btn-primary" style="margin-top: 16px; min-width: 200px;">Continue Shopping</a>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
cartItems.innerHTML = this.cart
|
||||
.map(
|
||||
(item) => `
|
||||
<div class="cart-item">
|
||||
<div class="cart-item-image">
|
||||
<img src="${item.image || "/assets/images/placeholder.jpg"}" alt="${
|
||||
item.name
|
||||
}">
|
||||
</div>
|
||||
<div class="cart-item-info">
|
||||
<div class="cart-item-name">${item.name}</div>
|
||||
${
|
||||
item.color
|
||||
? `<div class="cart-item-color" style="font-size: 0.85rem; color: #666;">Color: ${item.color}</div>`
|
||||
: ""
|
||||
}
|
||||
<div class="cart-item-price">$${parseFloat(item.price).toFixed(
|
||||
2,
|
||||
)}</div>
|
||||
<div class="cart-item-qty">
|
||||
<button class="qty-btn" onclick="SkyArtShop.updateCartQty('${
|
||||
item.id
|
||||
}', -1)">-</button>
|
||||
<span>${item.quantity}</span>
|
||||
<button class="qty-btn" onclick="SkyArtShop.updateCartQty('${
|
||||
item.id
|
||||
}', 1)">+</button>
|
||||
<button class="qty-btn" onclick="SkyArtShop.removeFromCart('${
|
||||
item.id
|
||||
}')" style="margin-left: auto; color: #e74c3c;">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
if (cartTotal) {
|
||||
cartTotal.textContent = `$${this.getCartTotal().toFixed(2)}`;
|
||||
}
|
||||
},
|
||||
|
||||
// Wishlist Functionality
|
||||
initWishlist() {
|
||||
this.updateWishlistCount();
|
||||
},
|
||||
|
||||
toggleWishlist(product) {
|
||||
const index = this.wishlist.findIndex((item) => item.id === product.id);
|
||||
|
||||
if (index > -1) {
|
||||
this.wishlist.splice(index, 1);
|
||||
this.showNotification(`${product.name} removed from wishlist`);
|
||||
} else {
|
||||
this.wishlist.push(product);
|
||||
this.showNotification(`${product.name} added to wishlist!`);
|
||||
}
|
||||
|
||||
this.saveWishlist();
|
||||
this.updateWishlistCount();
|
||||
this.updateWishlistButtons();
|
||||
},
|
||||
|
||||
isInWishlist(productId) {
|
||||
return this.wishlist.some((item) => item.id === productId);
|
||||
},
|
||||
|
||||
saveWishlist() {
|
||||
localStorage.setItem("skyart_wishlist", JSON.stringify(this.wishlist));
|
||||
},
|
||||
|
||||
updateWishlistCount() {
|
||||
const count = this.wishlist.length;
|
||||
document.querySelectorAll(".wishlist-count").forEach((el) => {
|
||||
el.textContent = count;
|
||||
el.style.display = count > 0 ? "flex" : "none";
|
||||
});
|
||||
},
|
||||
|
||||
updateWishlistButtons() {
|
||||
document.querySelectorAll(".wishlist-btn").forEach((btn) => {
|
||||
const productId = btn.dataset.productId;
|
||||
if (this.isInWishlist(productId)) {
|
||||
btn.classList.add("active");
|
||||
btn.innerHTML = '<i class="bi bi-heart-fill"></i>';
|
||||
} else {
|
||||
btn.classList.remove("active");
|
||||
btn.innerHTML = '<i class="bi bi-heart"></i>';
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Wishlist Drawer
|
||||
initWishlistDrawer() {
|
||||
const wishlistBtn = document.querySelector(".wishlist-btn-nav");
|
||||
const wishlistDrawer = document.querySelector(".wishlist-drawer");
|
||||
const wishlistOverlay = document.querySelector(".wishlist-overlay");
|
||||
const wishlistClose = document.querySelector(".wishlist-close");
|
||||
|
||||
if (wishlistBtn && wishlistDrawer) {
|
||||
wishlistBtn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.openWishlist();
|
||||
});
|
||||
}
|
||||
|
||||
if (wishlistClose)
|
||||
wishlistClose.addEventListener("click", () => this.closeWishlist());
|
||||
if (wishlistOverlay)
|
||||
wishlistOverlay.addEventListener("click", () => this.closeWishlist());
|
||||
|
||||
// Close on escape
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") this.closeWishlist();
|
||||
});
|
||||
},
|
||||
|
||||
openWishlist() {
|
||||
const wishlistDrawer = document.querySelector(".wishlist-drawer");
|
||||
const wishlistOverlay = document.querySelector(".wishlist-overlay");
|
||||
if (wishlistDrawer) wishlistDrawer.classList.add("open");
|
||||
if (wishlistOverlay) wishlistOverlay.classList.add("open");
|
||||
document.body.style.overflow = "hidden";
|
||||
this.renderWishlist();
|
||||
},
|
||||
|
||||
closeWishlist() {
|
||||
const wishlistDrawer = document.querySelector(".wishlist-drawer");
|
||||
const wishlistOverlay = document.querySelector(".wishlist-overlay");
|
||||
if (wishlistDrawer) wishlistDrawer.classList.remove("open");
|
||||
if (wishlistOverlay) wishlistOverlay.classList.remove("open");
|
||||
document.body.style.overflow = "";
|
||||
},
|
||||
|
||||
renderWishlist() {
|
||||
const wishlistItems = document.querySelector(".wishlist-items");
|
||||
if (!wishlistItems) return;
|
||||
|
||||
if (this.wishlist.length === 0) {
|
||||
wishlistItems.innerHTML = `
|
||||
<div class="wishlist-empty">
|
||||
<i class="bi bi-heart"></i>
|
||||
<p>Your wishlist is empty</p>
|
||||
<p style="font-size: 0.9rem;">Browse our products and add items you love!</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
wishlistItems.innerHTML = this.wishlist
|
||||
.map(
|
||||
(item) => `
|
||||
<div class="wishlist-item" data-id="${item.id}">
|
||||
<div class="wishlist-item-image">
|
||||
<img src="${item.image || "/uploads/default-product.png"}" alt="${
|
||||
item.name
|
||||
}">
|
||||
</div>
|
||||
<div class="wishlist-item-info">
|
||||
<div class="wishlist-item-name">${item.name}</div>
|
||||
${
|
||||
item.color
|
||||
? `<div class="wishlist-item-color" style="font-size: 0.85rem; color: #666;">Color: ${item.color}</div>`
|
||||
: ""
|
||||
}
|
||||
<div class="wishlist-item-price">$${parseFloat(item.price).toFixed(
|
||||
2,
|
||||
)}</div>
|
||||
<div class="wishlist-item-actions">
|
||||
<button class="wishlist-add-to-cart" onclick="SkyArtShop.moveToCart('${
|
||||
item.id
|
||||
}')">
|
||||
<i class="bi bi-cart-plus"></i> Add to Cart
|
||||
</button>
|
||||
<button class="wishlist-remove" onclick="SkyArtShop.removeFromWishlistById('${
|
||||
item.id
|
||||
}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
},
|
||||
|
||||
moveToCart(productId) {
|
||||
const item = this.wishlist.find((item) => item.id === productId);
|
||||
if (item) {
|
||||
// Pass the full item including color and image
|
||||
this.addToCart({
|
||||
id: item.id,
|
||||
productId: item.productId || item.id,
|
||||
name: item.name,
|
||||
price: item.price,
|
||||
image: item.image,
|
||||
color: item.color || null,
|
||||
});
|
||||
this.removeFromWishlistById(productId);
|
||||
}
|
||||
},
|
||||
|
||||
removeFromWishlistById(productId) {
|
||||
const index = this.wishlist.findIndex((item) => item.id === productId);
|
||||
if (index > -1) {
|
||||
const item = this.wishlist[index];
|
||||
this.wishlist.splice(index, 1);
|
||||
this.saveWishlist();
|
||||
this.updateWishlistCount();
|
||||
this.updateWishlistButtons();
|
||||
this.renderWishlist();
|
||||
this.showNotification(`${item.name} removed from wishlist`);
|
||||
}
|
||||
},
|
||||
|
||||
// Products
|
||||
initProducts() {
|
||||
// Attach event listeners to product cards
|
||||
document.querySelectorAll(".add-to-cart-btn").forEach((btn) => {
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
const card = btn.closest(".product-card");
|
||||
const product = this.getProductFromCard(card);
|
||||
this.addToCart(product);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll(".wishlist-btn").forEach((btn) => {
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
const card = btn.closest(".product-card");
|
||||
const product = this.getProductFromCard(card);
|
||||
this.toggleWishlist(product);
|
||||
});
|
||||
});
|
||||
|
||||
this.updateWishlistButtons();
|
||||
},
|
||||
|
||||
getProductFromCard(card) {
|
||||
return {
|
||||
id: card.dataset.productId,
|
||||
name:
|
||||
card.querySelector(".product-name a")?.textContent ||
|
||||
card.querySelector(".product-name")?.textContent ||
|
||||
"Product",
|
||||
price:
|
||||
card.dataset.productPrice ||
|
||||
card.querySelector(".price-current")?.textContent?.replace("$", "") ||
|
||||
"0",
|
||||
image: card.querySelector(".product-image img")?.src || "",
|
||||
};
|
||||
},
|
||||
|
||||
// Animations
|
||||
initAnimations() {
|
||||
// Intersection Observer for scroll animations
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add("fade-in");
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: 0.1 },
|
||||
);
|
||||
|
||||
document
|
||||
.querySelectorAll(".section, .product-card, .blog-card, .portfolio-card")
|
||||
.forEach((el) => {
|
||||
observer.observe(el);
|
||||
});
|
||||
},
|
||||
|
||||
// Notifications
|
||||
showNotification(message, type = "success") {
|
||||
// Remove existing notifications
|
||||
document.querySelectorAll(".notification").forEach((n) => n.remove());
|
||||
|
||||
const notification = document.createElement("div");
|
||||
notification.className = `notification notification-${type}`;
|
||||
notification.innerHTML = `
|
||||
<i class="bi bi-${
|
||||
type === "success" ? "check-circle" : "exclamation-circle"
|
||||
}"></i>
|
||||
<span>${message}</span>
|
||||
`;
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background: ${type === "success" ? "#202023" : "#e74c3c"};
|
||||
color: white;
|
||||
padding: 16px 24px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
|
||||
z-index: 9999;
|
||||
animation: slideIn 0.3s ease;
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
notification.style.animation = "slideOut 0.3s ease forwards";
|
||||
setTimeout(() => notification.remove(), 300);
|
||||
}, 3000);
|
||||
},
|
||||
};
|
||||
|
||||
// Add notification animations
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `
|
||||
@keyframes slideIn {
|
||||
from { transform: translateX(100%); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
@keyframes slideOut {
|
||||
from { transform: translateX(0); opacity: 1; }
|
||||
to { transform: translateX(100%); opacity: 0; }
|
||||
}
|
||||
.cart-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 300px;
|
||||
text-align: center;
|
||||
color: var(--text-light);
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Initialize on DOM ready
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
SkyArtShop.init();
|
||||
});
|
||||
|
||||
// API Functions
|
||||
const API = {
|
||||
baseUrl: "/api",
|
||||
|
||||
async get(endpoint, noCache = false) {
|
||||
try {
|
||||
const url = noCache
|
||||
? `${this.baseUrl}${endpoint}${endpoint.includes("?") ? "&" : "?"}_t=${Date.now()}`
|
||||
: `${this.baseUrl}${endpoint}`;
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
return data.success ? data : null;
|
||||
} catch (error) {
|
||||
console.error("API Error:", error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async loadFeaturedProducts() {
|
||||
const data = await this.get("/products/featured?limit=4");
|
||||
return data?.products || [];
|
||||
},
|
||||
|
||||
async loadAllProducts() {
|
||||
const data = await this.get("/products");
|
||||
return data?.products || [];
|
||||
},
|
||||
|
||||
async loadProduct(slug) {
|
||||
// Always fetch fresh product data to get latest color variants
|
||||
const data = await this.get(`/products/${slug}`, true);
|
||||
return data?.product || null;
|
||||
},
|
||||
|
||||
async loadHomepageSections() {
|
||||
const data = await this.get("/homepage/sections");
|
||||
return data?.sections || [];
|
||||
},
|
||||
|
||||
async loadBlogPosts() {
|
||||
const data = await this.get("/blog/posts");
|
||||
return data?.posts || [];
|
||||
},
|
||||
|
||||
async loadPortfolioProjects() {
|
||||
const data = await this.get("/portfolio/projects", true);
|
||||
return data?.projects || [];
|
||||
},
|
||||
|
||||
async loadTeamMembers() {
|
||||
const data = await this.get("/team-members");
|
||||
return data?.teamMembers || [];
|
||||
},
|
||||
|
||||
async loadCategories() {
|
||||
const data = await this.get("/categories");
|
||||
return data?.categories || [];
|
||||
},
|
||||
};
|
||||
|
||||
// Product Renderer
|
||||
const ProductRenderer = {
|
||||
renderCard(product) {
|
||||
const primaryImage =
|
||||
product.images?.find((img) => img.is_primary) || product.images?.[0];
|
||||
const imageUrl =
|
||||
primaryImage?.image_url ||
|
||||
product.imageurl ||
|
||||
"/assets/images/placeholder.jpg";
|
||||
const inWishlist = SkyArtShop.isInWishlist(product.id);
|
||||
|
||||
return `
|
||||
<div class="product-card" data-product-id="${
|
||||
product.id
|
||||
}" data-product-slug="${
|
||||
product.slug || product.id
|
||||
}" data-product-price="${product.price}" style="cursor: pointer;">
|
||||
<div class="product-image">
|
||||
<img src="${imageUrl}" alt="${product.name}" loading="lazy">
|
||||
${
|
||||
product.isfeatured
|
||||
? '<div class="product-badges"><span class="product-badge new">Featured</span></div>'
|
||||
: ""
|
||||
}
|
||||
<div class="product-actions">
|
||||
<button class="product-action-btn wishlist-btn ${
|
||||
inWishlist ? "active" : ""
|
||||
}" data-product-id="${product.id}" title="Add to Wishlist">
|
||||
<i class="bi bi-heart${inWishlist ? "-fill" : ""}"></i>
|
||||
</button>
|
||||
<button class="product-action-btn quick-view-btn" title="Quick View">
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="product-info">
|
||||
<div class="product-category">${product.category || "General"}</div>
|
||||
<h3 class="product-name">${product.name}</h3>
|
||||
<div class="product-price">
|
||||
<span class="price-current">$${parseFloat(product.price).toFixed(
|
||||
2,
|
||||
)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="product-footer">
|
||||
<span class="product-stock ${
|
||||
product.stockquantity > 0
|
||||
? product.stockquantity < 10
|
||||
? "low-stock"
|
||||
: "in-stock"
|
||||
: ""
|
||||
}">
|
||||
${
|
||||
product.stockquantity > 0
|
||||
? product.stockquantity < 10
|
||||
? `Only ${product.stockquantity} left`
|
||||
: "In Stock"
|
||||
: "Out of Stock"
|
||||
}
|
||||
</span>
|
||||
<button class="add-to-cart-btn" ${
|
||||
product.stockquantity <= 0 ? "disabled" : ""
|
||||
}>
|
||||
<i class="bi bi-cart-plus"></i> Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
async renderProducts(container, products) {
|
||||
if (!container) return;
|
||||
|
||||
if (products.length === 0) {
|
||||
container.innerHTML = '<p class="text-center">No products found.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = products.map((p) => this.renderCard(p)).join("");
|
||||
SkyArtShop.initProducts();
|
||||
},
|
||||
};
|
||||
|
||||
// Blog Renderer
|
||||
const BlogRenderer = {
|
||||
renderCard(post) {
|
||||
const date = new Date(post.createdat).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
|
||||
return `
|
||||
<article class="blog-card">
|
||||
<div class="blog-image">
|
||||
<a href="/blog/${post.slug}">
|
||||
<img src="${
|
||||
post.imageurl || "/assets/images/blog-placeholder.jpg"
|
||||
}" alt="${post.title}" loading="lazy">
|
||||
</a>
|
||||
</div>
|
||||
<div class="blog-content">
|
||||
<div class="blog-meta">
|
||||
<span><i class="bi bi-calendar3"></i> ${date}</span>
|
||||
</div>
|
||||
<h3 class="blog-title">
|
||||
<a href="/blog/${post.slug}">${post.title}</a>
|
||||
</h3>
|
||||
<p class="blog-excerpt">${post.excerpt || ""}</p>
|
||||
<a href="/blog/${post.slug}" class="blog-read-more">
|
||||
Read More <i class="bi bi-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
`;
|
||||
},
|
||||
};
|
||||
|
||||
// Portfolio Renderer
|
||||
const PortfolioRenderer = {
|
||||
renderCard(project) {
|
||||
return `
|
||||
<div class="portfolio-card" data-project-id="${project.id}">
|
||||
<img src="${
|
||||
project.featuredimage || "/assets/images/portfolio-placeholder.jpg"
|
||||
}" alt="${project.title}" loading="lazy">
|
||||
<div class="portfolio-overlay">
|
||||
<h3>${project.title}</h3>
|
||||
<p>${project.description || ""}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
};
|
||||
|
||||
// Export for global use
|
||||
window.SkyArtShop = SkyArtShop;
|
||||
window.API = API;
|
||||
window.ProductRenderer = ProductRenderer;
|
||||
window.BlogRenderer = BlogRenderer;
|
||||
window.PortfolioRenderer = PortfolioRenderer;
|
||||
File diff suppressed because it is too large
Load Diff
842
website/public/account.html
Normal file
842
website/public/account.html
Normal file
@@ -0,0 +1,842 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>My Account | Sky Art Shop</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
/>
|
||||
<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=Poppins:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--color-bg-main: #ffebeb;
|
||||
--color-bg-secondary: #ffd0d0;
|
||||
--color-bg-promotion: #f6ccde;
|
||||
--color-accent: #fcb1d8;
|
||||
--color-text-main: #202023;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Poppins", -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #ffd0d0 0%, #ffebeb 100%);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
background: var(--color-bg-secondary);
|
||||
padding: 16px 32px;
|
||||
box-shadow: 0 2px 12px rgba(252, 177, 216, 0.2);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.logo img {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.logo span {
|
||||
color: var(--color-text-main);
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.3px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: var(--color-text-main);
|
||||
text-decoration: none;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
background: white;
|
||||
border: 2px solid #f0f0f0;
|
||||
color: #ef4444;
|
||||
padding: 10px 20px;
|
||||
border-radius: 10px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-family: inherit;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.logout-btn:hover {
|
||||
background: #fef2f2;
|
||||
border-color: #ef4444;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.main-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 32px;
|
||||
}
|
||||
|
||||
/* Welcome Section */
|
||||
.welcome-section {
|
||||
background: white;
|
||||
border-radius: 24px;
|
||||
padding: 36px 40px;
|
||||
margin-bottom: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
box-shadow: 0 8px 32px rgba(252, 177, 216, 0.15);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: linear-gradient(135deg, #fcb1d8 0%, #f6ccde 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2rem;
|
||||
color: var(--color-text-main);
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.welcome-text h1 {
|
||||
color: var(--color-text-main);
|
||||
font-size: 1.6rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.welcome-text p {
|
||||
color: #666;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* Stats Grid */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 20px rgba(252, 177, 216, 0.12);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 32px rgba(252, 177, 216, 0.25);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(252, 177, 216, 0.3) 0%,
|
||||
rgba(246, 204, 222, 0.3) 100%
|
||||
);
|
||||
border-radius: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 14px;
|
||||
font-size: 1.3rem;
|
||||
color: #e85a9c;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-main);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #888;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Content Grid */
|
||||
.content-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.content-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.content-card {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 20px rgba(252, 177, 216, 0.12);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(252, 177, 216, 0.08) 0%,
|
||||
rgba(246, 204, 222, 0.08) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.card-header h3 {
|
||||
color: var(--color-text-main);
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.card-header h3 i {
|
||||
color: #e85a9c;
|
||||
}
|
||||
|
||||
.view-all {
|
||||
color: #e85a9c;
|
||||
text-decoration: none;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.view-all:hover {
|
||||
color: #d14485;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 14px;
|
||||
opacity: 0.4;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.empty-state .btn-browse {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: linear-gradient(135deg, #fcb1d8 0%, #f6ccde 100%);
|
||||
color: var(--color-text-main);
|
||||
text-decoration: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 10px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.empty-state .btn-browse:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(252, 177, 216, 0.4);
|
||||
}
|
||||
|
||||
/* Item List */
|
||||
.item-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 14px;
|
||||
background: #fafafa;
|
||||
border-radius: 12px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.item-image {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(252, 177, 216, 0.2) 0%,
|
||||
rgba(246, 204, 222, 0.2) 100%
|
||||
);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.item-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.item-image i {
|
||||
font-size: 1.4rem;
|
||||
color: #e85a9c;
|
||||
}
|
||||
|
||||
.item-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
color: var(--color-text-main);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.item-price {
|
||||
color: #e85a9c;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.item-qty {
|
||||
color: #888;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Profile Section */
|
||||
.profile-card {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.profile-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.profile-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.profile-field label {
|
||||
color: #888;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.profile-field .value {
|
||||
color: var(--color-text-main);
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.edit-profile-btn {
|
||||
margin-top: 24px;
|
||||
background: linear-gradient(135deg, #fcb1d8 0%, #f6ccde 100%);
|
||||
color: var(--color-text-main);
|
||||
border: none;
|
||||
padding: 14px 28px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.edit-profile-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(252, 177, 216, 0.4);
|
||||
}
|
||||
|
||||
/* Loading State */
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 60vh;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(252, 177, 216, 0.3);
|
||||
border-top-color: var(--color-accent);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Toast */
|
||||
.toast {
|
||||
position: fixed;
|
||||
top: 24px;
|
||||
right: 24px;
|
||||
padding: 16px 24px;
|
||||
border-radius: 12px;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
font-size: 0.95rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
transform: translateX(120%);
|
||||
transition: transform 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
z-index: 1000;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.toast.show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
.toast.success {
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
}
|
||||
.toast.error {
|
||||
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||||
}
|
||||
.toast.info {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Toast -->
|
||||
<div class="toast" id="toast">
|
||||
<i class="bi bi-check-circle"></i>
|
||||
<span id="toastMessage">Message</span>
|
||||
</div>
|
||||
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<a href="/" class="logo">
|
||||
<img
|
||||
src="/uploads/cat-logo-only-1766962993568-201212396.png"
|
||||
alt="Sky Art Shop"
|
||||
/>
|
||||
<span>Sky Art Shop</span>
|
||||
</a>
|
||||
<div class="header-actions">
|
||||
<a href="/" class="nav-link">Shop</a>
|
||||
<a href="/portfolio" class="nav-link">Portfolio</a>
|
||||
<button class="logout-btn" onclick="logout()">
|
||||
<i class="bi bi-box-arrow-right"></i> Sign Out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div class="loading" id="loadingState">
|
||||
<div class="loading-spinner"></div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content" id="mainContent" style="display: none">
|
||||
<!-- Welcome Section -->
|
||||
<section class="welcome-section">
|
||||
<div class="avatar" id="userAvatar">J</div>
|
||||
<div class="welcome-text">
|
||||
<h1>Welcome back, <span id="userName">User</span>!</h1>
|
||||
<p>Manage your account, view your cart, and track your orders</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Stats Grid -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<i class="bi bi-cart3"></i>
|
||||
</div>
|
||||
<div class="stat-value" id="cartCount">0</div>
|
||||
<div class="stat-label">Cart Items</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<i class="bi bi-heart"></i>
|
||||
</div>
|
||||
<div class="stat-value" id="wishlistCount">0</div>
|
||||
<div class="stat-label">Wishlist</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
</div>
|
||||
<div class="stat-value" id="ordersCount">0</div>
|
||||
<div class="stat-label">Orders</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<i class="bi bi-star"></i>
|
||||
</div>
|
||||
<div class="stat-value" id="reviewsCount">0</div>
|
||||
<div class="stat-label">Reviews</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Grid -->
|
||||
<div class="content-grid">
|
||||
<!-- Cart -->
|
||||
<div class="content-card">
|
||||
<div class="card-header">
|
||||
<h3><i class="bi bi-cart3"></i> My Cart</h3>
|
||||
<a href="/cart" class="view-all">View All</a>
|
||||
</div>
|
||||
<div class="card-content" id="cartContent">
|
||||
<div class="empty-state">
|
||||
<i class="bi bi-cart3"></i>
|
||||
<p>Your cart is empty</p>
|
||||
<a href="/" class="btn-browse">
|
||||
<i class="bi bi-shop"></i> Browse Shop
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Wishlist -->
|
||||
<div class="content-card">
|
||||
<div class="card-header">
|
||||
<h3><i class="bi bi-heart"></i> My Wishlist</h3>
|
||||
<a href="/wishlist" class="view-all">View All</a>
|
||||
</div>
|
||||
<div class="card-content" id="wishlistContent">
|
||||
<div class="empty-state">
|
||||
<i class="bi bi-heart"></i>
|
||||
<p>Your wishlist is empty</p>
|
||||
<a href="/" class="btn-browse">
|
||||
<i class="bi bi-search"></i> Discover Art
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Profile -->
|
||||
<div class="content-card profile-card">
|
||||
<div class="card-header">
|
||||
<h3><i class="bi bi-person"></i> Profile Information</h3>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="profile-grid">
|
||||
<div class="profile-field">
|
||||
<label>First Name</label>
|
||||
<div class="value" id="profileFirstName">-</div>
|
||||
</div>
|
||||
<div class="profile-field">
|
||||
<label>Last Name</label>
|
||||
<div class="value" id="profileLastName">-</div>
|
||||
</div>
|
||||
<div class="profile-field">
|
||||
<label>Email Address</label>
|
||||
<div class="value" id="profileEmail">-</div>
|
||||
</div>
|
||||
<div class="profile-field">
|
||||
<label>Member Since</label>
|
||||
<div class="value" id="profileSince">-</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="edit-profile-btn"
|
||||
onclick="showToast('Profile editing coming soon!', 'info')"
|
||||
>
|
||||
<i class="bi bi-pencil"></i> Edit Profile
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
let customer = null;
|
||||
|
||||
function showToast(message, type = "success") {
|
||||
const toast = document.getElementById("toast");
|
||||
const toastMessage = document.getElementById("toastMessage");
|
||||
const icon = toast.querySelector("i");
|
||||
|
||||
toastMessage.textContent = message;
|
||||
toast.className = "toast " + type;
|
||||
|
||||
icon.className = "bi";
|
||||
if (type === "success") icon.classList.add("bi-check-circle");
|
||||
else if (type === "error") icon.classList.add("bi-exclamation-circle");
|
||||
else if (type === "info") icon.classList.add("bi-info-circle");
|
||||
|
||||
toast.classList.add("show");
|
||||
setTimeout(() => toast.classList.remove("show"), 4000);
|
||||
}
|
||||
|
||||
function logout() {
|
||||
localStorage.removeItem("customer");
|
||||
fetch("/api/customers/logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}).finally(() => {
|
||||
window.location.href = "/signin";
|
||||
});
|
||||
}
|
||||
|
||||
async function loadAccountData() {
|
||||
const stored = localStorage.getItem("customer");
|
||||
|
||||
if (!stored) {
|
||||
window.location.href = "/signin";
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
customer = JSON.parse(stored);
|
||||
} catch (e) {
|
||||
window.location.href = "/signin";
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById("userName").textContent =
|
||||
customer.firstName || "User";
|
||||
document.getElementById("userAvatar").textContent = (
|
||||
customer.firstName || "U"
|
||||
)
|
||||
.charAt(0)
|
||||
.toUpperCase();
|
||||
document.getElementById("profileFirstName").textContent =
|
||||
customer.firstName || "-";
|
||||
document.getElementById("profileLastName").textContent =
|
||||
customer.lastName || "-";
|
||||
document.getElementById("profileEmail").textContent =
|
||||
customer.email || "-";
|
||||
|
||||
if (customer.createdAt) {
|
||||
const date = new Date(customer.createdAt);
|
||||
document.getElementById("profileSince").textContent =
|
||||
date.toLocaleDateString("en-US", {
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const [cartRes, wishlistRes] = await Promise.all([
|
||||
fetch("/api/customers/cart/count", { credentials: "include" }),
|
||||
fetch("/api/customers/wishlist/count", { credentials: "include" }),
|
||||
]);
|
||||
|
||||
if (cartRes.ok) {
|
||||
const cartData = await cartRes.json();
|
||||
document.getElementById("cartCount").textContent =
|
||||
cartData.count || 0;
|
||||
}
|
||||
|
||||
if (wishlistRes.ok) {
|
||||
const wishlistData = await wishlistRes.json();
|
||||
document.getElementById("wishlistCount").textContent =
|
||||
wishlistData.count || 0;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error loading counts:", e);
|
||||
}
|
||||
|
||||
try {
|
||||
const cartItemsRes = await fetch("/api/customers/cart", {
|
||||
credentials: "include",
|
||||
});
|
||||
if (cartItemsRes.ok) {
|
||||
const cartItems = await cartItemsRes.json();
|
||||
if (cartItems.items && cartItems.items.length > 0) {
|
||||
renderCartItems(cartItems.items.slice(0, 3));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error loading cart items:", e);
|
||||
}
|
||||
|
||||
try {
|
||||
const wishlistRes = await fetch("/api/customers/wishlist", {
|
||||
credentials: "include",
|
||||
});
|
||||
if (wishlistRes.ok) {
|
||||
const wishlistItems = await wishlistRes.json();
|
||||
if (wishlistItems.items && wishlistItems.items.length > 0) {
|
||||
renderWishlistItems(wishlistItems.items.slice(0, 3));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error loading wishlist:", e);
|
||||
}
|
||||
|
||||
document.getElementById("loadingState").style.display = "none";
|
||||
document.getElementById("mainContent").style.display = "block";
|
||||
}
|
||||
|
||||
function renderCartItems(items) {
|
||||
const container = document.getElementById("cartContent");
|
||||
if (!items || items.length === 0) return;
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="item-list">
|
||||
${items
|
||||
.map(
|
||||
(item) => `
|
||||
<div class="item">
|
||||
<div class="item-image">
|
||||
${
|
||||
item.product_image
|
||||
? `<img src="${item.product_image}" alt="${item.product_name}">`
|
||||
: `<i class="bi bi-image"></i>`
|
||||
}
|
||||
</div>
|
||||
<div class="item-details">
|
||||
<div class="item-name">${item.product_name || "Product"}</div>
|
||||
<div class="item-price">$${parseFloat(item.price || 0).toFixed(
|
||||
2
|
||||
)}</div>
|
||||
<div class="item-qty">Qty: ${item.quantity || 1}</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("")}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderWishlistItems(items) {
|
||||
const container = document.getElementById("wishlistContent");
|
||||
if (!items || items.length === 0) return;
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="item-list">
|
||||
${items
|
||||
.map(
|
||||
(item) => `
|
||||
<div class="item">
|
||||
<div class="item-image">
|
||||
${
|
||||
item.product_image
|
||||
? `<img src="${item.product_image}" alt="${item.product_name}">`
|
||||
: `<i class="bi bi-image"></i>`
|
||||
}
|
||||
</div>
|
||||
<div class="item-details">
|
||||
<div class="item-name">${item.product_name || "Product"}</div>
|
||||
<div class="item-price">$${parseFloat(item.price || 0).toFixed(
|
||||
2
|
||||
)}</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("")}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", loadAccountData);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1819
website/public/assets/css/mobile-fixes.css
Normal file
1819
website/public/assets/css/mobile-fixes.css
Normal file
File diff suppressed because it is too large
Load Diff
2696
website/public/assets/css/modern-theme.css
Normal file
2696
website/public/assets/css/modern-theme.css
Normal file
File diff suppressed because it is too large
Load Diff
65
website/public/assets/images/logo/cat-logo.svg
Normal file
65
website/public/assets/images/logo/cat-logo.svg
Normal file
@@ -0,0 +1,65 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
|
||||
<!-- Gradient definitions -->
|
||||
<defs>
|
||||
<linearGradient id="catGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#FF6B9D;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#C239B3;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="accentGradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#FEC6DF;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#FF6B9D;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background circle -->
|
||||
<circle cx="100" cy="100" r="95" fill="url(#accentGradient)" opacity="0.2"/>
|
||||
|
||||
<!-- Cat body -->
|
||||
<ellipse cx="100" cy="130" rx="45" ry="50" fill="url(#catGradient)"/>
|
||||
|
||||
<!-- Cat head -->
|
||||
<circle cx="100" cy="80" r="35" fill="url(#catGradient)"/>
|
||||
|
||||
<!-- Left ear -->
|
||||
<path d="M 75 55 L 65 30 L 85 50 Z" fill="url(#catGradient)"/>
|
||||
<path d="M 75 55 L 70 35 L 82 52 Z" fill="#FEC6DF"/>
|
||||
|
||||
<!-- Right ear -->
|
||||
<path d="M 125 55 L 135 30 L 115 50 Z" fill="url(#catGradient)"/>
|
||||
<path d="M 125 55 L 130 35 L 118 52 Z" fill="#FEC6DF"/>
|
||||
|
||||
<!-- Left eye -->
|
||||
<ellipse cx="88" cy="75" rx="6" ry="10" fill="#2D3436"/>
|
||||
<ellipse cx="89" cy="73" rx="2" ry="3" fill="white"/>
|
||||
|
||||
<!-- Right eye -->
|
||||
<ellipse cx="112" cy="75" rx="6" ry="10" fill="#2D3436"/>
|
||||
<ellipse cx="113" cy="73" rx="2" ry="3" fill="white"/>
|
||||
|
||||
<!-- Nose -->
|
||||
<path d="M 100 85 L 97 90 L 103 90 Z" fill="#FF6B9D"/>
|
||||
|
||||
<!-- Mouth -->
|
||||
<path d="M 100 90 Q 95 93 92 91" stroke="#2D3436" stroke-width="1.5" fill="none" stroke-linecap="round"/>
|
||||
<path d="M 100 90 Q 105 93 108 91" stroke="#2D3436" stroke-width="1.5" fill="none" stroke-linecap="round"/>
|
||||
|
||||
<!-- Whiskers left -->
|
||||
<line x1="70" y1="80" x2="50" y2="78" stroke="#2D3436" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<line x1="70" y1="85" x2="50" y2="85" stroke="#2D3436" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<line x1="70" y1="90" x2="50" y2="92" stroke="#2D3436" stroke-width="1.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- Whiskers right -->
|
||||
<line x1="130" y1="80" x2="150" y2="78" stroke="#2D3436" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<line x1="130" y1="85" x2="150" y2="85" stroke="#2D3436" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<line x1="130" y1="90" x2="150" y2="92" stroke="#2D3436" stroke-width="1.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- Paws -->
|
||||
<ellipse cx="80" cy="170" rx="12" ry="8" fill="#FF6B9D"/>
|
||||
<ellipse cx="120" cy="170" rx="12" ry="8" fill="#FF6B9D"/>
|
||||
|
||||
<!-- Tail -->
|
||||
<path d="M 140 140 Q 160 130 165 110 Q 168 90 160 75" stroke="url(#catGradient)" stroke-width="12" fill="none" stroke-linecap="round"/>
|
||||
|
||||
<!-- Heart on chest (art/creativity symbol) -->
|
||||
<path d="M 100 120 L 95 115 Q 93 112 93 109 Q 93 106 95 104 Q 97 102 100 104 Q 103 102 105 104 Q 107 106 107 109 Q 107 112 105 115 Z" fill="#FEC6DF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
616
website/public/assets/js/accessibility.js
Normal file
616
website/public/assets/js/accessibility.js
Normal file
@@ -0,0 +1,616 @@
|
||||
/**
|
||||
* Accessibility Enhancements
|
||||
* Adds ARIA labels, focus states, keyboard navigation, and screen reader support
|
||||
*/
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
console.log("[Accessibility] Loading...");
|
||||
|
||||
const A11y = {
|
||||
init() {
|
||||
this.addARIALabels();
|
||||
this.enhanceKeyboardNavigation();
|
||||
this.addSkipLinks();
|
||||
this.enhanceFocusStates();
|
||||
this.announceLiveRegions();
|
||||
this.fixMobileAccessibility();
|
||||
console.log("[Accessibility] Initialized");
|
||||
},
|
||||
|
||||
// Add ARIA labels to interactive elements
|
||||
addARIALabels() {
|
||||
// Navbar
|
||||
const navbar = document.querySelector(".navbar");
|
||||
if (navbar) {
|
||||
navbar.setAttribute("role", "navigation");
|
||||
navbar.setAttribute("aria-label", "Main navigation");
|
||||
}
|
||||
|
||||
// Nav menu
|
||||
const navMenu = document.querySelector(".nav-menu");
|
||||
if (navMenu) {
|
||||
navMenu.setAttribute("role", "menubar");
|
||||
navMenu.setAttribute("aria-label", "Primary menu");
|
||||
|
||||
navMenu.querySelectorAll(".nav-link").forEach((link, index) => {
|
||||
link.setAttribute("role", "menuitem");
|
||||
link.setAttribute("tabindex", "0");
|
||||
});
|
||||
}
|
||||
|
||||
// Nav actions buttons
|
||||
const navActions = document.querySelector(".nav-actions");
|
||||
if (navActions) {
|
||||
navActions.setAttribute("role", "group");
|
||||
navActions.setAttribute("aria-label", "Account and cart actions");
|
||||
}
|
||||
|
||||
// Cart button
|
||||
const cartBtn = document.querySelector(".cart-btn");
|
||||
if (cartBtn) {
|
||||
cartBtn.setAttribute("aria-label", "Shopping cart");
|
||||
cartBtn.setAttribute("aria-haspopup", "dialog");
|
||||
const cartCount = cartBtn.querySelector(".cart-count");
|
||||
if (cartCount) {
|
||||
cartBtn.setAttribute("aria-describedby", "cart-count-desc");
|
||||
// Create hidden description for screen readers
|
||||
if (!document.getElementById("cart-count-desc")) {
|
||||
const desc = document.createElement("span");
|
||||
desc.id = "cart-count-desc";
|
||||
desc.className = "sr-only";
|
||||
desc.textContent = "items in cart";
|
||||
cartBtn.appendChild(desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wishlist button
|
||||
const wishlistBtn = document.querySelector(".wishlist-btn-nav");
|
||||
if (wishlistBtn) {
|
||||
wishlistBtn.setAttribute("aria-label", "Wishlist");
|
||||
wishlistBtn.setAttribute("aria-haspopup", "dialog");
|
||||
}
|
||||
|
||||
// Sign in link
|
||||
const signinLink = document.querySelector('a[href="/signin"]');
|
||||
if (signinLink) {
|
||||
signinLink.setAttribute("aria-label", "Sign in to your account");
|
||||
}
|
||||
|
||||
// Mobile menu toggle
|
||||
const mobileToggle = document.querySelector(".nav-mobile-toggle");
|
||||
if (mobileToggle) {
|
||||
mobileToggle.setAttribute("aria-expanded", "false");
|
||||
mobileToggle.setAttribute("aria-controls", "nav-menu");
|
||||
mobileToggle.setAttribute("aria-label", "Toggle navigation menu");
|
||||
}
|
||||
|
||||
// Product cards
|
||||
document.querySelectorAll(".product-card").forEach((card, index) => {
|
||||
card.setAttribute("role", "article");
|
||||
const title = card.querySelector(".product-title, .product-name, h3");
|
||||
if (title) {
|
||||
card.setAttribute("aria-label", title.textContent.trim());
|
||||
}
|
||||
|
||||
// Quick view button
|
||||
const quickView = card.querySelector(
|
||||
'.quick-view-btn, [data-action="quick-view"]'
|
||||
);
|
||||
if (quickView) {
|
||||
quickView.setAttribute(
|
||||
"aria-label",
|
||||
`Quick view ${title ? title.textContent.trim() : "product"}`
|
||||
);
|
||||
}
|
||||
|
||||
// Add to cart button
|
||||
const addCart = card.querySelector(
|
||||
'.add-to-cart-btn, [data-action="add-to-cart"]'
|
||||
);
|
||||
if (addCart) {
|
||||
addCart.setAttribute(
|
||||
"aria-label",
|
||||
`Add ${title ? title.textContent.trim() : "product"} to cart`
|
||||
);
|
||||
}
|
||||
|
||||
// Wishlist button
|
||||
const wishlist = card.querySelector(
|
||||
'.wishlist-btn, [data-action="wishlist"]'
|
||||
);
|
||||
if (wishlist) {
|
||||
wishlist.setAttribute(
|
||||
"aria-label",
|
||||
`Add ${title ? title.textContent.trim() : "product"} to wishlist`
|
||||
);
|
||||
wishlist.setAttribute("aria-pressed", "false");
|
||||
}
|
||||
});
|
||||
|
||||
// Slider controls
|
||||
const sliderPrev = document.querySelector(".slider-arrow.prev");
|
||||
const sliderNext = document.querySelector(".slider-arrow.next");
|
||||
if (sliderPrev) sliderPrev.setAttribute("aria-label", "Previous slide");
|
||||
if (sliderNext) sliderNext.setAttribute("aria-label", "Next slide");
|
||||
|
||||
// Slider nav dots
|
||||
document
|
||||
.querySelectorAll(".slider-nav .dot, .slider-dot")
|
||||
.forEach((dot, index) => {
|
||||
dot.setAttribute("role", "tab");
|
||||
dot.setAttribute("aria-label", `Go to slide ${index + 1}`);
|
||||
dot.setAttribute(
|
||||
"aria-selected",
|
||||
dot.classList.contains("active") ? "true" : "false"
|
||||
);
|
||||
});
|
||||
|
||||
// Cart drawer
|
||||
const cartDrawer = document.querySelector(".cart-drawer");
|
||||
if (cartDrawer) {
|
||||
cartDrawer.setAttribute("role", "dialog");
|
||||
cartDrawer.setAttribute("aria-modal", "true");
|
||||
cartDrawer.setAttribute("aria-label", "Shopping cart");
|
||||
|
||||
const closeBtn = cartDrawer.querySelector(".cart-close, .close-cart");
|
||||
if (closeBtn) {
|
||||
closeBtn.setAttribute("aria-label", "Close cart");
|
||||
}
|
||||
}
|
||||
|
||||
// Wishlist drawer
|
||||
const wishlistDrawer = document.querySelector(".wishlist-drawer");
|
||||
if (wishlistDrawer) {
|
||||
wishlistDrawer.setAttribute("role", "dialog");
|
||||
wishlistDrawer.setAttribute("aria-modal", "true");
|
||||
wishlistDrawer.setAttribute("aria-label", "Wishlist");
|
||||
|
||||
const closeBtn = wishlistDrawer.querySelector(
|
||||
".wishlist-close, .close-wishlist"
|
||||
);
|
||||
if (closeBtn) {
|
||||
closeBtn.setAttribute("aria-label", "Close wishlist");
|
||||
}
|
||||
}
|
||||
|
||||
// Form inputs
|
||||
document
|
||||
.querySelectorAll("input:not([aria-label]):not([aria-labelledby])")
|
||||
.forEach((input) => {
|
||||
const label =
|
||||
input.closest("label") ||
|
||||
document.querySelector(`label[for="${input.id}"]`);
|
||||
if (label) {
|
||||
if (!input.id) {
|
||||
input.id = `input-${Math.random().toString(36).substr(2, 9)}`;
|
||||
label.setAttribute("for", input.id);
|
||||
}
|
||||
} else {
|
||||
const placeholder = input.getAttribute("placeholder");
|
||||
if (placeholder) {
|
||||
input.setAttribute("aria-label", placeholder);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Images without alt text
|
||||
document.querySelectorAll("img:not([alt])").forEach((img) => {
|
||||
img.setAttribute("alt", "");
|
||||
img.setAttribute("role", "presentation");
|
||||
});
|
||||
|
||||
// Footer
|
||||
const footer = document.querySelector("footer, .footer");
|
||||
if (footer) {
|
||||
footer.setAttribute("role", "contentinfo");
|
||||
}
|
||||
|
||||
// Main content
|
||||
const main = document.querySelector("main, .page-content");
|
||||
if (main) {
|
||||
main.setAttribute("role", "main");
|
||||
main.id = main.id || "main-content";
|
||||
}
|
||||
|
||||
// Sections
|
||||
document.querySelectorAll("section").forEach((section) => {
|
||||
const heading = section.querySelector("h1, h2, h3");
|
||||
if (heading) {
|
||||
section.setAttribute(
|
||||
"aria-labelledby",
|
||||
heading.id ||
|
||||
(heading.id = `heading-${Math.random()
|
||||
.toString(36)
|
||||
.substr(2, 9)}`)
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Enhance keyboard navigation
|
||||
enhanceKeyboardNavigation() {
|
||||
// ESC key to close modals/drawers
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") {
|
||||
// Close cart drawer
|
||||
const cartDrawer = document.querySelector(
|
||||
".cart-drawer.open, .cart-drawer.active"
|
||||
);
|
||||
if (cartDrawer) {
|
||||
const closeBtn = cartDrawer.querySelector(
|
||||
".cart-close, .close-cart"
|
||||
);
|
||||
if (closeBtn) closeBtn.click();
|
||||
document.querySelector(".cart-btn")?.focus();
|
||||
}
|
||||
|
||||
// Close wishlist drawer
|
||||
const wishlistDrawer = document.querySelector(
|
||||
".wishlist-drawer.open, .wishlist-drawer.active"
|
||||
);
|
||||
if (wishlistDrawer) {
|
||||
const closeBtn = wishlistDrawer.querySelector(
|
||||
".wishlist-close, .close-wishlist"
|
||||
);
|
||||
if (closeBtn) closeBtn.click();
|
||||
document.querySelector(".wishlist-btn-nav")?.focus();
|
||||
}
|
||||
|
||||
// Close mobile menu
|
||||
const navMenu = document.querySelector(
|
||||
".nav-menu.open, .nav-menu.active"
|
||||
);
|
||||
if (navMenu) {
|
||||
document.querySelector(".nav-mobile-toggle")?.click();
|
||||
}
|
||||
|
||||
// Close modals
|
||||
const modal = document.querySelector(".modal.show, .modal.open");
|
||||
if (modal) {
|
||||
const closeBtn = modal.querySelector(
|
||||
'.modal-close, .close-modal, [data-dismiss="modal"]'
|
||||
);
|
||||
if (closeBtn) closeBtn.click();
|
||||
}
|
||||
|
||||
// Close user dropdown
|
||||
const userDropdown = document.querySelector(
|
||||
".user-dropdown-menu.show"
|
||||
);
|
||||
if (userDropdown) {
|
||||
userDropdown.classList.remove("show");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Arrow key navigation for nav menu
|
||||
const navLinks = document.querySelectorAll(".nav-menu .nav-link");
|
||||
navLinks.forEach((link, index) => {
|
||||
link.addEventListener("keydown", (e) => {
|
||||
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
const next = navLinks[index + 1] || navLinks[0];
|
||||
next.focus();
|
||||
} else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
const prev = navLinks[index - 1] || navLinks[navLinks.length - 1];
|
||||
prev.focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Enter/Space for clickable elements
|
||||
document
|
||||
.querySelectorAll('[role="button"], [role="tab"], .product-card')
|
||||
.forEach((el) => {
|
||||
if (!el.getAttribute("tabindex")) {
|
||||
el.setAttribute("tabindex", "0");
|
||||
}
|
||||
|
||||
el.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
el.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Focus trap for modals/drawers
|
||||
this.setupFocusTrap(".cart-drawer");
|
||||
this.setupFocusTrap(".wishlist-drawer");
|
||||
this.setupFocusTrap(".modal");
|
||||
},
|
||||
|
||||
// Setup focus trap for modal-like elements
|
||||
setupFocusTrap(selector) {
|
||||
const container = document.querySelector(selector);
|
||||
if (!container) return;
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.attributeName === "class") {
|
||||
const isOpen =
|
||||
container.classList.contains("open") ||
|
||||
container.classList.contains("active") ||
|
||||
container.classList.contains("show");
|
||||
|
||||
if (isOpen) {
|
||||
this.trapFocus(container);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(container, { attributes: true });
|
||||
},
|
||||
|
||||
// Trap focus within container
|
||||
trapFocus(container) {
|
||||
const focusableElements = container.querySelectorAll(
|
||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||
);
|
||||
|
||||
if (focusableElements.length === 0) return;
|
||||
|
||||
const firstElement = focusableElements[0];
|
||||
const lastElement = focusableElements[focusableElements.length - 1];
|
||||
|
||||
firstElement.focus();
|
||||
|
||||
container.addEventListener("keydown", function trapHandler(e) {
|
||||
if (e.key !== "Tab") return;
|
||||
|
||||
if (e.shiftKey) {
|
||||
if (document.activeElement === firstElement) {
|
||||
e.preventDefault();
|
||||
lastElement.focus();
|
||||
}
|
||||
} else {
|
||||
if (document.activeElement === lastElement) {
|
||||
e.preventDefault();
|
||||
firstElement.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Add skip to main content link
|
||||
addSkipLinks() {
|
||||
if (document.querySelector(".skip-link")) return;
|
||||
|
||||
const main = document.querySelector("main, .page-content, #main-content");
|
||||
if (!main) return;
|
||||
|
||||
main.id = main.id || "main-content";
|
||||
|
||||
const skipLink = document.createElement("a");
|
||||
skipLink.href = "#" + main.id;
|
||||
skipLink.className = "skip-link";
|
||||
skipLink.textContent = "Skip to main content";
|
||||
skipLink.setAttribute("tabindex", "0");
|
||||
|
||||
document.body.insertBefore(skipLink, document.body.firstChild);
|
||||
|
||||
// Add CSS for skip link
|
||||
if (!document.getElementById("skip-link-styles")) {
|
||||
const style = document.createElement("style");
|
||||
style.id = "skip-link-styles";
|
||||
style.textContent = `
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
top: -100px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--primary-pink, #f8c8dc);
|
||||
color: var(--text-primary, #333);
|
||||
padding: 12px 24px;
|
||||
border-radius: 0 0 8px 8px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
z-index: 10000;
|
||||
transition: top 0.3s ease;
|
||||
}
|
||||
.skip-link:focus {
|
||||
top: 0;
|
||||
outline: 3px solid var(--accent-pink, #ff69b4);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
},
|
||||
|
||||
// Enhance focus states for better visibility
|
||||
enhanceFocusStates() {
|
||||
if (document.getElementById("focus-state-styles")) return;
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.id = "focus-state-styles";
|
||||
style.textContent = `
|
||||
/* Enhanced focus states for accessibility */
|
||||
:focus {
|
||||
outline: 2px solid var(--accent-pink, #ff69b4);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 3px solid var(--accent-pink, #ff69b4);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Button focus */
|
||||
.btn:focus-visible,
|
||||
button:focus-visible {
|
||||
outline: 3px solid var(--accent-pink, #ff69b4);
|
||||
outline-offset: 2px;
|
||||
box-shadow: 0 0 0 4px rgba(255, 105, 180, 0.25);
|
||||
}
|
||||
|
||||
/* Link focus */
|
||||
a:focus-visible {
|
||||
outline: 2px solid var(--accent-pink, #ff69b4);
|
||||
outline-offset: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Input focus */
|
||||
input:focus-visible,
|
||||
select:focus-visible,
|
||||
textarea:focus-visible {
|
||||
outline: 2px solid var(--primary-pink, #f8c8dc);
|
||||
outline-offset: 0;
|
||||
border-color: var(--accent-pink, #ff69b4);
|
||||
box-shadow: 0 0 0 3px rgba(248, 200, 220, 0.3);
|
||||
}
|
||||
|
||||
/* Card focus */
|
||||
.product-card:focus-visible,
|
||||
.blog-card:focus-visible {
|
||||
outline: 3px solid var(--accent-pink, #ff69b4);
|
||||
outline-offset: 4px;
|
||||
}
|
||||
|
||||
/* Nav link focus */
|
||||
.nav-link:focus-visible {
|
||||
background: rgba(248, 200, 220, 0.3);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
}
|
||||
|
||||
/* Icon button focus */
|
||||
.nav-icon-btn:focus-visible {
|
||||
outline: 2px solid var(--accent-pink, #ff69b4);
|
||||
outline-offset: 2px;
|
||||
background: rgba(248, 200, 220, 0.3);
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
},
|
||||
|
||||
// Setup live regions for dynamic content announcements
|
||||
announceLiveRegions() {
|
||||
// Create live region for announcements
|
||||
if (!document.getElementById("a11y-live-region")) {
|
||||
const liveRegion = document.createElement("div");
|
||||
liveRegion.id = "a11y-live-region";
|
||||
liveRegion.setAttribute("aria-live", "polite");
|
||||
liveRegion.setAttribute("aria-atomic", "true");
|
||||
liveRegion.className = "sr-only";
|
||||
document.body.appendChild(liveRegion);
|
||||
}
|
||||
|
||||
// Announce cart updates
|
||||
const originalAddToCart = window.ShopState?.addToCart;
|
||||
if (originalAddToCart) {
|
||||
window.ShopState.addToCart = function (...args) {
|
||||
const result = originalAddToCart.apply(this, args);
|
||||
A11y.announce("Item added to cart");
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
// Announce wishlist updates
|
||||
const originalAddToWishlist = window.ShopState?.addToWishlist;
|
||||
if (originalAddToWishlist) {
|
||||
window.ShopState.addToWishlist = function (...args) {
|
||||
const result = originalAddToWishlist.apply(this, args);
|
||||
A11y.announce("Item added to wishlist");
|
||||
return result;
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Announce message to screen readers
|
||||
announce(message) {
|
||||
const liveRegion = document.getElementById("a11y-live-region");
|
||||
if (liveRegion) {
|
||||
liveRegion.textContent = message;
|
||||
setTimeout(() => {
|
||||
liveRegion.textContent = "";
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
|
||||
// Mobile accessibility enhancements
|
||||
fixMobileAccessibility() {
|
||||
// Ensure touch targets are at least 44x44px
|
||||
const style = document.createElement("style");
|
||||
style.id = "mobile-a11y-styles";
|
||||
style.textContent = `
|
||||
@media (max-width: 768px) {
|
||||
/* Minimum touch target size */
|
||||
button,
|
||||
.btn,
|
||||
.nav-icon-btn,
|
||||
.nav-link,
|
||||
input[type="button"],
|
||||
input[type="submit"],
|
||||
a {
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
}
|
||||
|
||||
/* Ensure adequate spacing for touch */
|
||||
.nav-actions {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Larger tap targets for mobile menu */
|
||||
.nav-menu .nav-link {
|
||||
padding: 16px 20px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
if (!document.getElementById("mobile-a11y-styles")) {
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
// Update mobile toggle aria-expanded on click
|
||||
const mobileToggle = document.querySelector(".nav-mobile-toggle");
|
||||
if (mobileToggle) {
|
||||
const observer = new MutationObserver(() => {
|
||||
const navMenu = document.querySelector(".nav-menu");
|
||||
const isOpen =
|
||||
navMenu?.classList.contains("open") ||
|
||||
navMenu?.classList.contains("active") ||
|
||||
document.body.classList.contains("nav-open");
|
||||
mobileToggle.setAttribute("aria-expanded", isOpen ? "true" : "false");
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
attributes: true,
|
||||
subtree: true,
|
||||
attributeFilter: ["class"],
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize on DOM ready
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", () => A11y.init());
|
||||
} else {
|
||||
A11y.init();
|
||||
}
|
||||
|
||||
// Expose for external use
|
||||
window.A11y = A11y;
|
||||
})();
|
||||
421
website/public/assets/js/cart.js
Normal file
421
website/public/assets/js/cart.js
Normal file
@@ -0,0 +1,421 @@
|
||||
/**
|
||||
* Shopping Cart Component
|
||||
* Handles cart dropdown, updates, and interactions
|
||||
*/
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// Base Dropdown Component
|
||||
class BaseDropdown {
|
||||
constructor(config) {
|
||||
this.toggleBtn = document.getElementById(config.toggleId);
|
||||
this.panel = document.getElementById(config.panelId);
|
||||
this.content = document.getElementById(config.contentId);
|
||||
this.closeBtn = document.getElementById(config.closeId);
|
||||
this.wrapperClass = config.wrapperClass;
|
||||
this.eventName = config.eventName;
|
||||
this.emptyMessage = config.emptyMessage;
|
||||
this.isOpen = false;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.setupEventListeners();
|
||||
this.render();
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
if (this.toggleBtn) {
|
||||
this.toggleBtn.addEventListener("click", () => this.toggle());
|
||||
}
|
||||
|
||||
if (this.closeBtn) {
|
||||
this.closeBtn.addEventListener("click", () => this.close());
|
||||
}
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
if (this.isOpen && !e.target.closest(this.wrapperClass)) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener(this.eventName, () => {
|
||||
console.log(`[${this.constructor.name}] ${this.eventName} received`);
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.isOpen ? this.close() : this.open();
|
||||
}
|
||||
|
||||
open() {
|
||||
if (this.panel) {
|
||||
this.panel.classList.add("active");
|
||||
this.panel.setAttribute("aria-hidden", "false");
|
||||
this.isOpen = true;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.panel) {
|
||||
this.panel.classList.remove("active");
|
||||
this.panel.setAttribute("aria-hidden", "true");
|
||||
this.isOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
renderEmpty() {
|
||||
if (this.content) {
|
||||
this.content.innerHTML = this.emptyMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ShoppingCart extends BaseDropdown {
|
||||
constructor() {
|
||||
super({
|
||||
toggleId: "cartToggle",
|
||||
panelId: "cartPanel",
|
||||
contentId: "cartContent",
|
||||
closeId: "cartClose",
|
||||
wrapperClass: ".cart-dropdown-wrapper",
|
||||
eventName: "cart-updated",
|
||||
emptyMessage:
|
||||
'<p class="empty-state"><i class="bi bi-cart-x"></i><br>Your cart is empty</p>',
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.content) return;
|
||||
|
||||
try {
|
||||
if (!window.AppState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cart = window.AppState.cart;
|
||||
|
||||
if (!Array.isArray(cart)) {
|
||||
this.content.innerHTML =
|
||||
'<p class="empty-state">Error loading cart</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
if (cart.length === 0) {
|
||||
this.renderEmpty();
|
||||
this.updateFooter(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const validItems = this._filterValidItems(cart);
|
||||
if (validItems.length === 0) {
|
||||
this.renderEmpty();
|
||||
this.updateFooter(null);
|
||||
return;
|
||||
}
|
||||
|
||||
this.content.innerHTML = validItems
|
||||
.map((item) => this.renderCartItem(item))
|
||||
.join("");
|
||||
this.setupCartItemListeners();
|
||||
|
||||
const total = this._calculateTotal(validItems);
|
||||
this.updateFooter(total);
|
||||
} catch (error) {
|
||||
this.content.innerHTML =
|
||||
'<p class="empty-state">Error loading cart</p>';
|
||||
}
|
||||
}
|
||||
|
||||
_filterValidItems(items) {
|
||||
return items.filter(
|
||||
(item) => item && item.id && typeof item.price !== "undefined"
|
||||
);
|
||||
}
|
||||
|
||||
_calculateTotal(items) {
|
||||
if (window.AppState.getCartTotal) {
|
||||
return window.AppState.getCartTotal();
|
||||
}
|
||||
return items.reduce((sum, item) => {
|
||||
const price = parseFloat(item.price) || 0;
|
||||
const quantity = parseInt(item.quantity) || 0;
|
||||
return sum + price * quantity;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
renderCartItem(item) {
|
||||
try {
|
||||
// Validate item and Utils availability
|
||||
if (!item || !item.id) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!window.Utils) {
|
||||
return '<p class="error-message">Error loading item</p>';
|
||||
}
|
||||
|
||||
// Sanitize and validate item data with defensive checks
|
||||
const imageUrl =
|
||||
item.image ||
|
||||
item.imageurl ||
|
||||
item.imageUrl ||
|
||||
item.image_url ||
|
||||
"/assets/images/placeholder.svg";
|
||||
const title = window.Utils.escapeHtml(
|
||||
item.title || item.name || "Product"
|
||||
);
|
||||
const color = item.color ? window.Utils.escapeHtml(item.color) : null;
|
||||
const price = parseFloat(item.price) || 0;
|
||||
const quantity = Math.max(1, parseInt(item.quantity) || 1);
|
||||
const subtotal = price * quantity;
|
||||
|
||||
const priceFormatted = window.Utils.formatCurrency(price);
|
||||
const subtotalFormatted = window.Utils.formatCurrency(subtotal);
|
||||
|
||||
return `
|
||||
<div class="cart-item" data-id="${item.id}">
|
||||
<img src="${imageUrl}" alt="${title}" class="cart-item-image" loading="lazy" onerror="this.src='/assets/images/placeholder.svg'">
|
||||
<div class="cart-item-details">
|
||||
<h4 class="cart-item-title">${title}</h4>
|
||||
${
|
||||
color
|
||||
? `<p class="cart-item-color" style="font-size: 0.85rem; color: #666; margin: 2px 0;">Color: ${color}</p>`
|
||||
: ""
|
||||
}
|
||||
<p class="cart-item-price">${priceFormatted}</p>
|
||||
<div class="cart-item-quantity">
|
||||
<button class="quantity-btn quantity-minus" data-id="${
|
||||
item.id
|
||||
}" aria-label="Decrease quantity">
|
||||
<i class="bi bi-dash"></i>
|
||||
</button>
|
||||
<span class="quantity-value">${quantity}</span>
|
||||
<button class="quantity-btn quantity-plus" data-id="${
|
||||
item.id
|
||||
}" aria-label="Increase quantity">
|
||||
<i class="bi bi-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="cart-item-subtotal">Subtotal: ${subtotalFormatted}</p>
|
||||
</div>
|
||||
<button class="cart-item-remove" data-id="${
|
||||
item.id
|
||||
}" aria-label="Remove from cart">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
setupCartItemListeners() {
|
||||
try {
|
||||
this._setupRemoveButtons();
|
||||
this._setupQuantityButtons();
|
||||
} catch (error) {
|
||||
console.error("[ShoppingCart] Error setting up listeners:", error);
|
||||
}
|
||||
}
|
||||
|
||||
_setupRemoveButtons() {
|
||||
this.content.querySelectorAll(".cart-item-remove").forEach((btn) => {
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
this._handleAction(e, () => {
|
||||
const id = e.currentTarget.dataset.id;
|
||||
if (id && window.AppState?.removeFromCart) {
|
||||
window.AppState.removeFromCart(id);
|
||||
this.render();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_setupQuantityButtons() {
|
||||
this._setupQuantityButton(".quantity-minus", -1);
|
||||
this._setupQuantityButton(".quantity-plus", 1);
|
||||
}
|
||||
|
||||
_setupQuantityButton(selector, delta) {
|
||||
this.content.querySelectorAll(selector).forEach((btn) => {
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
this._handleAction(e, () => {
|
||||
const id = e.currentTarget.dataset.id;
|
||||
if (!window.AppState?.cart) return;
|
||||
|
||||
const item = window.AppState.cart.find(
|
||||
(item) => String(item.id) === String(id)
|
||||
);
|
||||
|
||||
if (!item || !window.AppState.updateCartQuantity) return;
|
||||
|
||||
const newQuantity =
|
||||
delta > 0
|
||||
? Math.min(item.quantity + delta, 999)
|
||||
: Math.max(item.quantity + delta, 1);
|
||||
|
||||
if (delta < 0 && item.quantity <= 1) return;
|
||||
|
||||
window.AppState.updateCartQuantity(id, newQuantity);
|
||||
this.render();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_handleAction(event, callback) {
|
||||
try {
|
||||
callback();
|
||||
} catch (error) {
|
||||
console.error("[ShoppingCart] Action error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
updateFooter(total) {
|
||||
const footer = this.cartPanel?.querySelector(".dropdown-foot");
|
||||
if (!footer) return;
|
||||
|
||||
if (total === null) {
|
||||
footer.innerHTML =
|
||||
'<a href="/shop" class="btn-outline">Continue Shopping</a>';
|
||||
} else {
|
||||
footer.innerHTML = `
|
||||
<a href="/shop" class="btn-outline">Continue Shopping</a>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wishlist Component
|
||||
class Wishlist extends BaseDropdown {
|
||||
constructor() {
|
||||
super({
|
||||
toggleId: "wishlistToggle",
|
||||
panelId: "wishlistPanel",
|
||||
contentId: "wishlistContent",
|
||||
closeId: "wishlistClose",
|
||||
wrapperClass: ".wishlist-dropdown-wrapper",
|
||||
eventName: "wishlist-updated",
|
||||
emptyMessage:
|
||||
'<p class="empty-state"><i class="bi bi-heart"></i><br>Your wishlist is empty</p>',
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.content) return;
|
||||
|
||||
if (!window.AppState) {
|
||||
console.warn("[Wishlist] AppState not available yet");
|
||||
return;
|
||||
}
|
||||
|
||||
const wishlist = window.AppState.wishlist;
|
||||
|
||||
if (wishlist.length === 0) {
|
||||
this.renderEmpty();
|
||||
return;
|
||||
}
|
||||
|
||||
this.content.innerHTML = wishlist
|
||||
.map((item) => this.renderWishlistItem(item))
|
||||
.join("");
|
||||
|
||||
this.setupWishlistItemListeners();
|
||||
}
|
||||
|
||||
renderWishlistItem(item) {
|
||||
if (!window.Utils) {
|
||||
console.error("[Wishlist] Utils not available");
|
||||
return '<p class="error-message">Error loading item</p>';
|
||||
}
|
||||
|
||||
const imageUrl =
|
||||
item.imageurl ||
|
||||
item.imageUrl ||
|
||||
item.image_url ||
|
||||
"/assets/images/placeholder.jpg";
|
||||
const title = window.Utils.escapeHtml(
|
||||
item.title || item.name || "Product"
|
||||
);
|
||||
const price = window.Utils.formatCurrency(parseFloat(item.price) || 0);
|
||||
|
||||
return `
|
||||
<div class="wishlist-item" data-id="${item.id}">
|
||||
<img src="${imageUrl}" alt="${title}" class="wishlist-item-image" loading="lazy" onerror="this.src='/assets/images/placeholder.svg'">
|
||||
<div class="wishlist-item-details">
|
||||
<h4 class="wishlist-item-title">${title}</h4>
|
||||
<p class="wishlist-item-price">${price}</p>
|
||||
<button class="btn-add-to-cart" data-id="${item.id}">
|
||||
<i class="bi bi-cart-plus"></i> Add to Cart
|
||||
</button>
|
||||
</div>
|
||||
<button class="wishlist-item-remove" data-id="${item.id}" aria-label="Remove from wishlist">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
setupWishlistItemListeners() {
|
||||
this._setupRemoveButtons();
|
||||
this._setupAddToCartButtons();
|
||||
}
|
||||
|
||||
_setupRemoveButtons() {
|
||||
this.content.querySelectorAll(".wishlist-item-remove").forEach((btn) => {
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
const id = e.currentTarget.dataset.id;
|
||||
if (window.AppState?.removeFromWishlist) {
|
||||
window.AppState.removeFromWishlist(id);
|
||||
this.render();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_setupAddToCartButtons() {
|
||||
this.content.querySelectorAll(".btn-add-to-cart").forEach((btn) => {
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
const id = e.currentTarget.dataset.id;
|
||||
const item = window.AppState?.wishlist.find(
|
||||
(item) => String(item.id) === String(id)
|
||||
);
|
||||
if (item && window.AppState?.addToCart) {
|
||||
window.AppState.addToCart(item);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize when DOM is ready
|
||||
const initializeComponents = () => {
|
||||
// Skip if shop-system.js already initialized
|
||||
if (window.ShopSystem?.isInitialized) {
|
||||
console.log(
|
||||
"[cart.js] Skipping initialization - shop-system.js already loaded"
|
||||
);
|
||||
return;
|
||||
}
|
||||
console.log("[cart.js] Initializing ShoppingCart and Wishlist components");
|
||||
new ShoppingCart();
|
||||
new Wishlist();
|
||||
};
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", initializeComponents);
|
||||
} else {
|
||||
initializeComponents();
|
||||
}
|
||||
})();
|
||||
540
website/public/assets/js/components.js
Normal file
540
website/public/assets/js/components.js
Normal file
@@ -0,0 +1,540 @@
|
||||
/**
|
||||
* Shared HTML Components
|
||||
* Eliminates duplication across HTML files
|
||||
*/
|
||||
|
||||
// Component templates
|
||||
const Components = {
|
||||
/**
|
||||
* Render cart drawer HTML
|
||||
* @returns {string} Cart drawer HTML
|
||||
*/
|
||||
cartDrawer: () => `
|
||||
<div id="cartDrawer" class="cart-drawer">
|
||||
<div class="cart-drawer-header">
|
||||
<h3>Shopping Cart</h3>
|
||||
<button id="closeCart" class="close-cart" aria-label="Close cart">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="cartItems" class="cart-items"></div>
|
||||
<div class="cart-footer">
|
||||
<div class="cart-total">
|
||||
<span>Total:</span>
|
||||
<span id="cartTotal">$0.00</span>
|
||||
</div>
|
||||
<button id="checkoutBtn" class="btn btn-primary checkout-btn">
|
||||
Proceed to Checkout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
|
||||
/**
|
||||
* Render wishlist drawer HTML
|
||||
* @returns {string} Wishlist drawer HTML
|
||||
*/
|
||||
wishlistDrawer: () => `
|
||||
<div id="wishlistDrawer" class="wishlist-drawer">
|
||||
<div class="wishlist-drawer-header">
|
||||
<h3>Wishlist</h3>
|
||||
<button id="closeWishlist" class="close-wishlist" aria-label="Close wishlist">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="wishlistItems" class="wishlist-items"></div>
|
||||
</div>
|
||||
`,
|
||||
|
||||
/**
|
||||
* Render navbar HTML
|
||||
* @param {Object} options - Navbar options
|
||||
* @param {string} options.activePage - Current active page
|
||||
* @returns {string} Navbar HTML
|
||||
*/
|
||||
navbar: ({ activePage = "" } = {}) => {
|
||||
const isActive = (page) => (page === activePage ? "active" : "");
|
||||
return `
|
||||
<nav class="navbar">
|
||||
<div class="nav-container">
|
||||
<a href="/" class="nav-logo">
|
||||
<img src="/assets/images/logo.png" alt="SkyArtShop Logo" class="logo-img" />
|
||||
<span>SkyArtShop</span>
|
||||
</a>
|
||||
|
||||
<button class="mobile-menu-toggle" id="mobileMenuToggle" aria-label="Toggle menu">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
|
||||
<div class="nav-menu" id="navMenu">
|
||||
<a href="/" class="nav-link ${isActive("home")}">Home</a>
|
||||
<a href="/shop" class="nav-link ${isActive("shop")}">Shop</a>
|
||||
<a href="/portfolio" class="nav-link ${isActive(
|
||||
"portfolio"
|
||||
)}">Portfolio</a>
|
||||
<a href="/blog" class="nav-link ${isActive("blog")}">Blog</a>
|
||||
<a href="/about" class="nav-link ${isActive("about")}">About</a>
|
||||
<a href="/contact" class="nav-link ${isActive(
|
||||
"contact"
|
||||
)}">Contact</a>
|
||||
|
||||
<div class="nav-actions">
|
||||
<button id="wishlistBtn" class="icon-btn" aria-label="Wishlist">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
|
||||
</svg>
|
||||
<span id="wishlistBadge" class="badge">0</span>
|
||||
</button>
|
||||
|
||||
<button id="cartBtn" class="icon-btn" aria-label="Shopping cart">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="9" cy="21" r="1"></circle>
|
||||
<circle cx="20" cy="21" r="1"></circle>
|
||||
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
|
||||
</svg>
|
||||
<span id="cartBadge" class="badge">0</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Render footer HTML
|
||||
* @returns {string} Footer HTML
|
||||
*/
|
||||
footer: () => `
|
||||
<footer class="footer">
|
||||
<div class="footer-container">
|
||||
<div class="footer-section">
|
||||
<h3>About SkyArtShop</h3>
|
||||
<p>Your premier destination for unique art pieces and custom designs.</p>
|
||||
</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<h3>Quick Links</h3>
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/shop">Shop</a></li>
|
||||
<li><a href="/portfolio">Portfolio</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/about">About</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<h3>Customer Service</h3>
|
||||
<ul>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns</a></li>
|
||||
<li><a href="/privacy">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<h3>Connect With Us</h3>
|
||||
<div class="social-links">
|
||||
<a href="#" aria-label="Facebook">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#" aria-label="Instagram">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<rect x="2" y="2" width="20" height="20" rx="5" ry="5"></rect>
|
||||
<path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"></path>
|
||||
<line x1="17.5" y1="6.5" x2="17.51" y2="6.5"></line>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#" aria-label="Twitter">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-bottom">
|
||||
<p>© ${new Date().getFullYear()} SkyArtShop. All rights reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
`,
|
||||
|
||||
/**
|
||||
* Render notification container
|
||||
* @returns {string} Notification container HTML
|
||||
*/
|
||||
notificationContainer: () => `
|
||||
<div id="notificationContainer" class="notification-container"></div>
|
||||
`,
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize components in the DOM
|
||||
* @param {Object} options - Component options
|
||||
* @param {boolean} options.navbar - Include navbar
|
||||
* @param {boolean} options.footer - Include footer
|
||||
* @param {boolean} options.cart - Include cart drawer
|
||||
* @param {boolean} options.wishlist - Include wishlist drawer
|
||||
* @param {boolean} options.notifications - Include notifications
|
||||
* @param {string} options.activePage - Active page for navbar
|
||||
*/
|
||||
function initializeComponents({
|
||||
navbar = true,
|
||||
footer = true,
|
||||
cart = true,
|
||||
wishlist = true,
|
||||
notifications = true,
|
||||
activePage = "",
|
||||
} = {}) {
|
||||
// Inject components into DOM
|
||||
if (navbar) {
|
||||
const navPlaceholder = document.getElementById("navbar-placeholder");
|
||||
if (navPlaceholder) {
|
||||
navPlaceholder.innerHTML = Components.navbar({ activePage });
|
||||
}
|
||||
}
|
||||
|
||||
if (footer) {
|
||||
const footerPlaceholder = document.getElementById("footer-placeholder");
|
||||
if (footerPlaceholder) {
|
||||
footerPlaceholder.innerHTML = Components.footer();
|
||||
}
|
||||
}
|
||||
|
||||
if (cart) {
|
||||
const cartPlaceholder = document.getElementById("cart-drawer-placeholder");
|
||||
if (cartPlaceholder) {
|
||||
cartPlaceholder.innerHTML = Components.cartDrawer();
|
||||
}
|
||||
}
|
||||
|
||||
if (wishlist) {
|
||||
const wishlistPlaceholder = document.getElementById(
|
||||
"wishlist-drawer-placeholder"
|
||||
);
|
||||
if (wishlistPlaceholder) {
|
||||
wishlistPlaceholder.innerHTML = Components.wishlistDrawer();
|
||||
}
|
||||
}
|
||||
|
||||
if (notifications) {
|
||||
const notificationPlaceholder = document.getElementById(
|
||||
"notification-placeholder"
|
||||
);
|
||||
if (notificationPlaceholder) {
|
||||
notificationPlaceholder.innerHTML = Components.notificationContainer();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize mobile menu toggle
|
||||
initMobileMenu();
|
||||
|
||||
// Initialize cart and wishlist interactions
|
||||
if (cart) initCartDrawer();
|
||||
if (wishlist) initWishlistDrawer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize mobile menu functionality
|
||||
*/
|
||||
function initMobileMenu() {
|
||||
const menuToggle = document.getElementById("mobileMenuToggle");
|
||||
const navMenu = document.getElementById("navMenu");
|
||||
|
||||
if (menuToggle && navMenu) {
|
||||
menuToggle.addEventListener("click", () => {
|
||||
navMenu.classList.toggle("active");
|
||||
menuToggle.classList.toggle("active");
|
||||
});
|
||||
|
||||
// Close menu when clicking outside
|
||||
document.addEventListener("click", (e) => {
|
||||
if (
|
||||
!menuToggle.contains(e.target) &&
|
||||
!navMenu.contains(e.target) &&
|
||||
navMenu.classList.contains("active")
|
||||
) {
|
||||
navMenu.classList.remove("active");
|
||||
menuToggle.classList.remove("active");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize cart drawer functionality
|
||||
*/
|
||||
function initCartDrawer() {
|
||||
const cartBtn = document.getElementById("cartBtn");
|
||||
const cartDrawer = document.getElementById("cartDrawer");
|
||||
const closeCart = document.getElementById("closeCart");
|
||||
|
||||
if (cartBtn && cartDrawer) {
|
||||
cartBtn.addEventListener("click", () => {
|
||||
cartDrawer.classList.add("active");
|
||||
document.body.style.overflow = "hidden";
|
||||
updateCartUI();
|
||||
});
|
||||
|
||||
if (closeCart) {
|
||||
closeCart.addEventListener("click", () => {
|
||||
cartDrawer.classList.remove("active");
|
||||
document.body.style.overflow = "";
|
||||
});
|
||||
}
|
||||
|
||||
// Close on overlay click
|
||||
cartDrawer.addEventListener("click", (e) => {
|
||||
if (e.target === cartDrawer) {
|
||||
cartDrawer.classList.remove("active");
|
||||
document.body.style.overflow = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize checkout button
|
||||
const checkoutBtn = document.getElementById("checkoutBtn");
|
||||
if (checkoutBtn) {
|
||||
checkoutBtn.addEventListener("click", () => {
|
||||
window.location.href = "/checkout";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize wishlist drawer functionality
|
||||
*/
|
||||
function initWishlistDrawer() {
|
||||
const wishlistBtn = document.getElementById("wishlistBtn");
|
||||
const wishlistDrawer = document.getElementById("wishlistDrawer");
|
||||
const closeWishlist = document.getElementById("closeWishlist");
|
||||
|
||||
if (wishlistBtn && wishlistDrawer) {
|
||||
wishlistBtn.addEventListener("click", () => {
|
||||
wishlistDrawer.classList.add("active");
|
||||
document.body.style.overflow = "hidden";
|
||||
updateWishlistUI();
|
||||
});
|
||||
|
||||
if (closeWishlist) {
|
||||
closeWishlist.addEventListener("click", () => {
|
||||
wishlistDrawer.classList.remove("active");
|
||||
document.body.style.overflow = "";
|
||||
});
|
||||
}
|
||||
|
||||
// Close on overlay click
|
||||
wishlistDrawer.addEventListener("click", (e) => {
|
||||
if (e.target === wishlistDrawer) {
|
||||
wishlistDrawer.classList.remove("active");
|
||||
document.body.style.overflow = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update cart UI with current items
|
||||
*/
|
||||
function updateCartUI() {
|
||||
if (typeof CartUtils === "undefined") return;
|
||||
|
||||
const cart = CartUtils.getCart();
|
||||
const cartItems = document.getElementById("cartItems");
|
||||
const cartTotal = document.getElementById("cartTotal");
|
||||
const cartBadge = document.getElementById("cartBadge");
|
||||
|
||||
if (cartBadge) {
|
||||
cartBadge.textContent = cart.length;
|
||||
cartBadge.style.display = cart.length > 0 ? "flex" : "none";
|
||||
}
|
||||
|
||||
if (!cartItems) return;
|
||||
|
||||
if (cart.length === 0) {
|
||||
cartItems.innerHTML = '<p class="empty-cart">Your cart is empty</p>';
|
||||
if (cartTotal) cartTotal.textContent = "$0.00";
|
||||
return;
|
||||
}
|
||||
|
||||
cartItems.innerHTML = cart
|
||||
.map(
|
||||
(item) => `
|
||||
<div class="cart-item" data-id="${item.id}">
|
||||
<img src="${item.image}" alt="${item.name}" class="cart-item-image" />
|
||||
<div class="cart-item-details">
|
||||
<h4>${item.name}</h4>
|
||||
<p class="cart-item-price">${formatPrice(item.price)}</p>
|
||||
<div class="quantity-controls">
|
||||
<button class="qty-btn" onclick="decreaseQuantity('${
|
||||
item.id
|
||||
}')">-</button>
|
||||
<span class="quantity">${item.quantity}</span>
|
||||
<button class="qty-btn" onclick="increaseQuantity('${
|
||||
item.id
|
||||
}')">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="remove-item" onclick="removeFromCart('${
|
||||
item.id
|
||||
}')" aria-label="Remove item">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
|
||||
if (cartTotal) {
|
||||
cartTotal.textContent = formatPrice(CartUtils.getCartTotal());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update wishlist UI with current items
|
||||
*/
|
||||
function updateWishlistUI() {
|
||||
if (typeof WishlistUtils === "undefined") return;
|
||||
|
||||
const wishlist = WishlistUtils.getWishlist();
|
||||
const wishlistItems = document.getElementById("wishlistItems");
|
||||
const wishlistBadge = document.getElementById("wishlistBadge");
|
||||
|
||||
if (wishlistBadge) {
|
||||
wishlistBadge.textContent = wishlist.length;
|
||||
wishlistBadge.style.display = wishlist.length > 0 ? "flex" : "none";
|
||||
}
|
||||
|
||||
if (!wishlistItems) return;
|
||||
|
||||
if (wishlist.length === 0) {
|
||||
wishlistItems.innerHTML =
|
||||
'<p class="empty-wishlist">Your wishlist is empty</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
wishlistItems.innerHTML = wishlist
|
||||
.map(
|
||||
(item) => `
|
||||
<div class="wishlist-item" data-id="${item.id}">
|
||||
<img src="${item.image}" alt="${
|
||||
item.name
|
||||
}" class="wishlist-item-image" />
|
||||
<div class="wishlist-item-details">
|
||||
<h4>${item.name}</h4>
|
||||
<p class="wishlist-item-price">${formatPrice(item.price)}</p>
|
||||
<button class="btn-sm btn-primary" onclick="moveToCart('${item.id}')">
|
||||
Add to Cart
|
||||
</button>
|
||||
</div>
|
||||
<button class="remove-item" onclick="removeFromWishlist('${
|
||||
item.id
|
||||
}')" aria-label="Remove from wishlist">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
// Global functions for inline event handlers
|
||||
window.increaseQuantity = (productId) => {
|
||||
if (typeof CartUtils !== "undefined") {
|
||||
const cart = CartUtils.getCart();
|
||||
const item = cart.find((item) => item.id === productId);
|
||||
if (item) {
|
||||
CartUtils.updateQuantity(productId, item.quantity + 1);
|
||||
updateCartUI();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.decreaseQuantity = (productId) => {
|
||||
if (typeof CartUtils !== "undefined") {
|
||||
const cart = CartUtils.getCart();
|
||||
const item = cart.find((item) => item.id === productId);
|
||||
if (item && item.quantity > 1) {
|
||||
CartUtils.updateQuantity(productId, item.quantity - 1);
|
||||
updateCartUI();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.removeFromCart = (productId) => {
|
||||
if (typeof CartUtils !== "undefined") {
|
||||
CartUtils.removeFromCart(productId);
|
||||
updateCartUI();
|
||||
showNotification("Item removed from cart", "info");
|
||||
}
|
||||
};
|
||||
|
||||
window.removeFromWishlist = (productId) => {
|
||||
if (typeof WishlistUtils !== "undefined") {
|
||||
WishlistUtils.removeFromWishlist(productId);
|
||||
updateWishlistUI();
|
||||
showNotification("Item removed from wishlist", "info");
|
||||
}
|
||||
};
|
||||
|
||||
window.moveToCart = (productId) => {
|
||||
if (
|
||||
typeof WishlistUtils !== "undefined" &&
|
||||
typeof CartUtils !== "undefined"
|
||||
) {
|
||||
const wishlist = WishlistUtils.getWishlist();
|
||||
const item = wishlist.find((item) => item.id === productId);
|
||||
if (item) {
|
||||
CartUtils.addToCart(item);
|
||||
WishlistUtils.removeFromWishlist(productId);
|
||||
updateWishlistUI();
|
||||
updateCartUI();
|
||||
showNotification("Item moved to cart", "success");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize on DOM load
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Auto-initialize if data attribute is set
|
||||
const autoInit = document.body.dataset.autoInitComponents;
|
||||
if (autoInit !== "false") {
|
||||
initializeComponents({
|
||||
activePage: document.body.dataset.activePage || "",
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Already loaded
|
||||
const autoInit = document.body.dataset.autoInitComponents;
|
||||
if (autoInit !== "false") {
|
||||
initializeComponents({
|
||||
activePage: document.body.dataset.activePage || "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Export for manual initialization
|
||||
window.Components = Components;
|
||||
window.initializeComponents = initializeComponents;
|
||||
538
website/public/assets/js/customer-auth.js
Normal file
538
website/public/assets/js/customer-auth.js
Normal file
@@ -0,0 +1,538 @@
|
||||
/**
|
||||
* Customer Authentication State Manager
|
||||
* Handles checking login status and updating UI accordingly
|
||||
*/
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
console.log("[customer-auth.js] Loading...");
|
||||
|
||||
// Customer authentication state
|
||||
window.CustomerAuth = {
|
||||
user: null,
|
||||
isLoggedIn: false,
|
||||
isLoading: true,
|
||||
|
||||
// Initialize - check session on page load
|
||||
async init() {
|
||||
console.log("[CustomerAuth] Initializing...");
|
||||
await this.checkSession();
|
||||
this.updateNavbar();
|
||||
this.setupEventListeners();
|
||||
},
|
||||
|
||||
// Check if user is logged in
|
||||
async checkSession() {
|
||||
try {
|
||||
const response = await fetch("/api/customers/session", {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.success && data.loggedIn) {
|
||||
this.user = data.customer;
|
||||
this.isLoggedIn = true;
|
||||
console.log("[CustomerAuth] User logged in:", this.user.firstName);
|
||||
|
||||
// Sync local cart/wishlist with server
|
||||
await this.syncCartWithServer();
|
||||
await this.syncWishlistWithServer();
|
||||
} else {
|
||||
this.user = null;
|
||||
this.isLoggedIn = false;
|
||||
}
|
||||
} else {
|
||||
this.user = null;
|
||||
this.isLoggedIn = false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[CustomerAuth] Session check failed:", error);
|
||||
this.user = null;
|
||||
this.isLoggedIn = false;
|
||||
}
|
||||
this.isLoading = false;
|
||||
},
|
||||
|
||||
// Update navbar based on auth state
|
||||
updateNavbar() {
|
||||
// Find the sign in link/button in nav-actions
|
||||
const navActions = document.querySelector(".nav-actions");
|
||||
if (!navActions) {
|
||||
console.warn("[CustomerAuth] nav-actions not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the existing sign in link
|
||||
const signinLink = navActions.querySelector(
|
||||
'a[href="/signin"], a[href="/account"]',
|
||||
);
|
||||
|
||||
if (this.isLoggedIn && this.user) {
|
||||
// User is logged in - update UI
|
||||
if (signinLink) {
|
||||
// Replace with user dropdown
|
||||
const userDropdown = document.createElement("div");
|
||||
userDropdown.className = "user-dropdown";
|
||||
userDropdown.innerHTML = `
|
||||
<button class="nav-icon-btn user-btn" title="Account" id="userDropdownBtn">
|
||||
<i class="bi bi-person-check-fill"></i>
|
||||
<span class="user-name-short">${this.escapeHtml(
|
||||
this.user.firstName,
|
||||
)}</span>
|
||||
</button>
|
||||
<div class="user-dropdown-menu" id="userDropdownMenu">
|
||||
<div class="user-dropdown-header">
|
||||
<i class="bi bi-person-circle"></i>
|
||||
<div class="user-info">
|
||||
<span class="welcome-text">Welcome back,</span>
|
||||
<span class="user-name">${this.escapeHtml(
|
||||
this.user.firstName,
|
||||
)} ${this.escapeHtml(this.user.lastName || "")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-dropdown-divider"></div>
|
||||
<a href="/account" class="user-dropdown-item">
|
||||
<i class="bi bi-person"></i> My Account
|
||||
</a>
|
||||
<a href="/account#orders" class="user-dropdown-item">
|
||||
<i class="bi bi-bag-check"></i> My Orders
|
||||
</a>
|
||||
<a href="/account#wishlist" class="user-dropdown-item">
|
||||
<i class="bi bi-heart"></i> My Wishlist
|
||||
</a>
|
||||
<div class="user-dropdown-divider"></div>
|
||||
<button class="user-dropdown-item logout-btn" id="logoutBtn">
|
||||
<i class="bi bi-box-arrow-right"></i> Sign Out
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
signinLink.replaceWith(userDropdown);
|
||||
|
||||
// Add dropdown toggle
|
||||
const dropdownBtn = document.getElementById("userDropdownBtn");
|
||||
const dropdownMenu = document.getElementById("userDropdownMenu");
|
||||
const logoutBtn = document.getElementById("logoutBtn");
|
||||
|
||||
if (dropdownBtn && dropdownMenu) {
|
||||
dropdownBtn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
dropdownMenu.classList.toggle("show");
|
||||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener("click", () => {
|
||||
dropdownMenu.classList.remove("show");
|
||||
});
|
||||
}
|
||||
|
||||
if (logoutBtn) {
|
||||
logoutBtn.addEventListener("click", () => this.logout());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// User is not logged in - ensure sign in link is present
|
||||
if (signinLink) {
|
||||
// Update to show sign in icon
|
||||
signinLink.innerHTML = '<i class="bi bi-person"></i>';
|
||||
signinLink.href = "/signin";
|
||||
signinLink.title = "Sign In";
|
||||
}
|
||||
}
|
||||
|
||||
// Add user dropdown styles if not present
|
||||
this.addStyles();
|
||||
},
|
||||
|
||||
// Sync local cart with server when logged in
|
||||
async syncCartWithServer() {
|
||||
if (!this.isLoggedIn) return;
|
||||
|
||||
try {
|
||||
// Get server cart
|
||||
const response = await fetch("/api/customers/cart", {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.success && data.items) {
|
||||
// Merge with local cart
|
||||
const localCart = JSON.parse(
|
||||
localStorage.getItem("skyart_cart") || "[]",
|
||||
);
|
||||
|
||||
// If local cart has items, push them to server
|
||||
for (const item of localCart) {
|
||||
const exists = data.items.find((i) => i.productId === item.id);
|
||||
if (!exists) {
|
||||
await this.addToServerCart(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Update local cart with server cart
|
||||
const mergedCart = data.items.map((item) => ({
|
||||
id: item.productId,
|
||||
name: item.name,
|
||||
price: item.price,
|
||||
image: item.image,
|
||||
quantity: item.quantity,
|
||||
variantColor: item.variantColor,
|
||||
variantSize: item.variantSize,
|
||||
}));
|
||||
|
||||
localStorage.setItem("skyart_cart", JSON.stringify(mergedCart));
|
||||
if (window.AppState) {
|
||||
window.AppState.cart = mergedCart;
|
||||
window.AppState.updateUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[CustomerAuth] Cart sync failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// Sync local wishlist with server
|
||||
async syncWishlistWithServer() {
|
||||
if (!this.isLoggedIn) return;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/customers/wishlist", {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.success && data.items) {
|
||||
// Merge with local wishlist
|
||||
const localWishlist = JSON.parse(
|
||||
localStorage.getItem("wishlist") || "[]",
|
||||
);
|
||||
|
||||
// Push local items to server
|
||||
for (const item of localWishlist) {
|
||||
const exists = data.items.find((i) => i.productId === item.id);
|
||||
if (!exists) {
|
||||
await this.addToServerWishlist(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Update local wishlist with server data
|
||||
const mergedWishlist = data.items.map((item) => ({
|
||||
id: item.productId,
|
||||
name: item.name,
|
||||
price: item.price,
|
||||
image: item.image,
|
||||
}));
|
||||
|
||||
localStorage.setItem("wishlist", JSON.stringify(mergedWishlist));
|
||||
if (window.AppState) {
|
||||
window.AppState.wishlist = mergedWishlist;
|
||||
window.AppState.updateUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[CustomerAuth] Wishlist sync failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// Add item to server cart
|
||||
async addToServerCart(item) {
|
||||
try {
|
||||
await fetch("/api/customers/cart", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
productId: item.id,
|
||||
quantity: item.quantity || 1,
|
||||
variantColor: item.variantColor,
|
||||
variantSize: item.variantSize,
|
||||
}),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[CustomerAuth] Add to server cart failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// Add item to server wishlist
|
||||
async addToServerWishlist(item) {
|
||||
try {
|
||||
await fetch("/api/customers/wishlist", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ productId: item.id }),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[CustomerAuth] Add to server wishlist failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// Setup event listeners for cart/wishlist changes
|
||||
setupEventListeners() {
|
||||
// Listen for cart changes to sync with server
|
||||
window.addEventListener("cart-updated", async (e) => {
|
||||
if (this.isLoggedIn && e.detail) {
|
||||
// Debounce sync
|
||||
clearTimeout(this._syncCartTimeout);
|
||||
this._syncCartTimeout = setTimeout(() => {
|
||||
this.syncCartChanges(e.detail);
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for wishlist changes
|
||||
window.addEventListener("wishlist-updated", async (e) => {
|
||||
if (this.isLoggedIn && e.detail) {
|
||||
clearTimeout(this._syncWishlistTimeout);
|
||||
this._syncWishlistTimeout = setTimeout(() => {
|
||||
this.syncWishlistChanges(e.detail);
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Sync cart changes with server
|
||||
async syncCartChanges(cart) {
|
||||
if (!this.isLoggedIn) return;
|
||||
// For now, just ensure items are added/removed
|
||||
// More sophisticated sync could be implemented
|
||||
},
|
||||
|
||||
// Sync wishlist changes with server
|
||||
async syncWishlistChanges(wishlist) {
|
||||
if (!this.isLoggedIn) return;
|
||||
// Sync wishlist changes
|
||||
},
|
||||
|
||||
// Logout
|
||||
async logout() {
|
||||
try {
|
||||
const response = await fetch("/api/customers/logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
this.user = null;
|
||||
this.isLoggedIn = false;
|
||||
|
||||
// Show notification
|
||||
if (window.AppState && window.AppState.showNotification) {
|
||||
window.AppState.showNotification(
|
||||
"You have been signed out",
|
||||
"info",
|
||||
);
|
||||
}
|
||||
|
||||
// Redirect to home or refresh
|
||||
window.location.href = "/home";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[CustomerAuth] Logout failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// Helper: Escape HTML
|
||||
escapeHtml(text) {
|
||||
if (!text) return "";
|
||||
const div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
},
|
||||
|
||||
// Add styles for user dropdown
|
||||
addStyles() {
|
||||
if (document.getElementById("customer-auth-styles")) return;
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.id = "customer-auth-styles";
|
||||
style.textContent = `
|
||||
.user-dropdown {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.user-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px !important;
|
||||
border-radius: 20px;
|
||||
background: var(--primary-pink-light, #fff5f7);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.user-btn:hover {
|
||||
background: var(--primary-pink, #ff85a2);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.user-btn i {
|
||||
font-size: 1.1rem;
|
||||
color: var(--primary-pink, #ff85a2);
|
||||
}
|
||||
|
||||
.user-btn:hover i {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.user-name-short {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #333);
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.user-btn:hover .user-name-short {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.user-dropdown-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 10px);
|
||||
right: 0;
|
||||
min-width: 220px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.15);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-10px);
|
||||
transition: all 0.2s ease;
|
||||
z-index: 1000;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.user-dropdown-menu.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.user-dropdown-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: var(--primary-pink-light, #fff5f7);
|
||||
}
|
||||
|
||||
.user-dropdown-header > i {
|
||||
font-size: 2rem;
|
||||
color: var(--primary-pink, #ff85a2);
|
||||
}
|
||||
|
||||
.user-dropdown-header .user-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.user-dropdown-header .welcome-text {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-light, #999);
|
||||
}
|
||||
|
||||
.user-dropdown-header .user-name {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #333);
|
||||
}
|
||||
|
||||
.user-dropdown-divider {
|
||||
height: 1px;
|
||||
background: var(--border-light, #eee);
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.user-dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
color: var(--text-primary, #333);
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: none;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.user-dropdown-item:hover {
|
||||
background: var(--primary-pink-light, #fff5f7);
|
||||
color: var(--primary-pink, #ff85a2);
|
||||
}
|
||||
|
||||
.user-dropdown-item i {
|
||||
font-size: 1rem;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.logout-btn:hover {
|
||||
background: #fdf2f2;
|
||||
color: #c0392b;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.user-name-short {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.user-btn {
|
||||
padding: 8px !important;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.user-dropdown {
|
||||
-webkit-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.user-dropdown-menu {
|
||||
right: -10px;
|
||||
min-width: 200px;
|
||||
position: fixed;
|
||||
top: 70px;
|
||||
right: 10px;
|
||||
-webkit-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
will-change: opacity, visibility;
|
||||
}
|
||||
|
||||
.user-dropdown-menu.show {
|
||||
transform: translateZ(0);
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize on DOM ready
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
window.CustomerAuth.init();
|
||||
});
|
||||
} else {
|
||||
window.CustomerAuth.init();
|
||||
}
|
||||
})();
|
||||
228
website/public/assets/js/dynamic-page.js
Normal file
228
website/public/assets/js/dynamic-page.js
Normal file
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* Dynamic Page Content Loader
|
||||
* Loads page content from the API and renders it with proper formatting
|
||||
*/
|
||||
|
||||
// Convert Quill Delta to HTML - accurate conversion matching backend format
|
||||
function convertDeltaToHtml(delta) {
|
||||
if (!delta || !delta.ops) return "";
|
||||
|
||||
let html = "";
|
||||
let currentLine = "";
|
||||
let inListType = null; // 'bullet' or 'ordered'
|
||||
|
||||
const ops = delta.ops;
|
||||
|
||||
for (let i = 0; i < ops.length; i++) {
|
||||
const op = ops[i];
|
||||
const nextOp = ops[i + 1];
|
||||
|
||||
if (typeof op.insert === "string") {
|
||||
const text = op.insert;
|
||||
const inlineAttrs = op.attributes || {};
|
||||
|
||||
// Check if this is a standalone newline with block attributes
|
||||
if (text === "\n") {
|
||||
const blockAttrs = inlineAttrs;
|
||||
|
||||
// Handle list transitions
|
||||
if (blockAttrs.list) {
|
||||
const newListType = blockAttrs.list;
|
||||
if (inListType !== newListType) {
|
||||
if (inListType) {
|
||||
html += inListType === "ordered" ? "</ol>" : "</ul>";
|
||||
}
|
||||
html += newListType === "ordered" ? "<ol>" : "<ul>";
|
||||
inListType = newListType;
|
||||
}
|
||||
html += `<li>${currentLine}</li>`;
|
||||
} else {
|
||||
// Close any open list
|
||||
if (inListType) {
|
||||
html += inListType === "ordered" ? "</ol>" : "</ul>";
|
||||
inListType = null;
|
||||
}
|
||||
|
||||
// Apply block formatting
|
||||
if (blockAttrs.header) {
|
||||
html += `<h${blockAttrs.header}>${currentLine}</h${blockAttrs.header}>`;
|
||||
} else if (blockAttrs.blockquote) {
|
||||
html += `<blockquote>${currentLine}</blockquote>`;
|
||||
} else if (blockAttrs["code-block"]) {
|
||||
html += `<pre><code>${currentLine}</code></pre>`;
|
||||
} else if (currentLine) {
|
||||
html += `<p>${currentLine}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
currentLine = "";
|
||||
} else {
|
||||
// Regular text - may contain embedded newlines
|
||||
const parts = text.split("\n");
|
||||
|
||||
for (let j = 0; j < parts.length; j++) {
|
||||
const part = parts[j];
|
||||
|
||||
// Format the text part
|
||||
if (part.length > 0) {
|
||||
let formatted = escapeHtml(part);
|
||||
|
||||
// Apply inline formatting
|
||||
if (inlineAttrs.bold) formatted = `<strong>${formatted}</strong>`;
|
||||
if (inlineAttrs.italic) formatted = `<em>${formatted}</em>`;
|
||||
if (inlineAttrs.underline) formatted = `<u>${formatted}</u>`;
|
||||
if (inlineAttrs.strike) formatted = `<s>${formatted}</s>`;
|
||||
if (inlineAttrs.code) formatted = `<code>${formatted}</code>`;
|
||||
if (inlineAttrs.link)
|
||||
formatted = `<a href="${inlineAttrs.link}" target="_blank" rel="noopener">${formatted}</a>`;
|
||||
|
||||
currentLine += formatted;
|
||||
}
|
||||
|
||||
// Handle embedded newlines (not the last part)
|
||||
if (j < parts.length - 1) {
|
||||
// Close any open list for embedded newlines
|
||||
if (inListType) {
|
||||
html += inListType === "ordered" ? "</ol>" : "</ul>";
|
||||
inListType = null;
|
||||
}
|
||||
if (currentLine) {
|
||||
html += `<p>${currentLine}</p>`;
|
||||
}
|
||||
currentLine = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (op.insert && op.insert.image) {
|
||||
// Flush pending content
|
||||
if (currentLine) {
|
||||
if (inListType) {
|
||||
html += `<li>${currentLine}</li>`;
|
||||
html += inListType === "ordered" ? "</ol>" : "</ul>";
|
||||
inListType = null;
|
||||
} else {
|
||||
html += `<p>${currentLine}</p>`;
|
||||
}
|
||||
currentLine = "";
|
||||
}
|
||||
html += `<img src="${op.insert.image}" class="content-image" alt="Content image">`;
|
||||
}
|
||||
}
|
||||
|
||||
// Flush remaining content
|
||||
if (inListType) {
|
||||
if (currentLine) html += `<li>${currentLine}</li>`;
|
||||
html += inListType === "ordered" ? "</ol>" : "</ul>";
|
||||
} else if (currentLine) {
|
||||
html += `<p>${currentLine}</p>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
return text
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
// Parse and render page content (handles both Delta JSON and raw HTML)
|
||||
function parsePageContent(content) {
|
||||
if (!content) return "<p>Content coming soon...</p>";
|
||||
|
||||
try {
|
||||
const delta = JSON.parse(content);
|
||||
if (delta.ops) {
|
||||
return convertDeltaToHtml(delta);
|
||||
}
|
||||
return content;
|
||||
} catch (e) {
|
||||
// Not JSON, return as-is (probably HTML)
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
// Load page content from API
|
||||
async function loadPageContent(slug, options = {}) {
|
||||
const {
|
||||
titleSelector = "#pageTitle",
|
||||
contentSelector = "#dynamicContent",
|
||||
staticSelector = "#staticContent",
|
||||
showLoading = true,
|
||||
} = options;
|
||||
|
||||
const dynamicContent = document.querySelector(contentSelector);
|
||||
const staticContent = document.querySelector(staticSelector);
|
||||
const titleElement = document.querySelector(titleSelector);
|
||||
|
||||
if (!dynamicContent) {
|
||||
console.warn("Dynamic content container not found:", contentSelector);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (showLoading) {
|
||||
dynamicContent.innerHTML = `
|
||||
<div style="text-align: center; padding: 40px;">
|
||||
<div class="loading-spinner" style="width: 40px; height: 40px; border: 3px solid rgba(252,177,216,0.2); border-top-color: #FCB1D8; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto;"></div>
|
||||
<p style="margin-top: 16px; color: #888;">Loading content...</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/pages/${slug}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.page) {
|
||||
const page = data.page;
|
||||
|
||||
// Update page title if provided
|
||||
if (page.title && titleElement) {
|
||||
titleElement.textContent = page.title;
|
||||
}
|
||||
|
||||
// Parse and render content
|
||||
const htmlContent = parsePageContent(page.content);
|
||||
dynamicContent.innerHTML = htmlContent;
|
||||
|
||||
// Hide static fallback if exists
|
||||
if (staticContent) {
|
||||
staticContent.style.display = "none";
|
||||
}
|
||||
|
||||
return page;
|
||||
} else {
|
||||
// Show static fallback
|
||||
dynamicContent.style.display = "none";
|
||||
if (staticContent) {
|
||||
staticContent.style.display = "block";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load page content:", error);
|
||||
dynamicContent.style.display = "none";
|
||||
if (staticContent) {
|
||||
staticContent.style.display = "block";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-initialize on DOMContentLoaded if data-page-slug is set
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const pageContainer = document.querySelector("[data-page-slug]");
|
||||
if (pageContainer) {
|
||||
const slug = pageContainer.dataset.pageSlug;
|
||||
loadPageContent(slug);
|
||||
}
|
||||
});
|
||||
|
||||
// Export for use in other scripts
|
||||
window.DynamicPage = {
|
||||
loadPageContent,
|
||||
parsePageContent,
|
||||
convertDeltaToHtml,
|
||||
};
|
||||
114
website/public/assets/js/mobile-touch-fix.js
Normal file
114
website/public/assets/js/mobile-touch-fix.js
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Mobile Touch Optimization
|
||||
* Eliminates double-tap behavior and ensures immediate response on mobile devices
|
||||
*/
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// Only apply on touch devices
|
||||
if (!("ontouchstart" in window)) return;
|
||||
|
||||
// Disable 300ms click delay on mobile
|
||||
let touchStartX, touchStartY;
|
||||
|
||||
// Add fastclick functionality for immediate response
|
||||
document.addEventListener(
|
||||
"touchstart",
|
||||
function (e) {
|
||||
touchStartX = e.touches[0].clientX;
|
||||
touchStartY = e.touches[0].clientY;
|
||||
},
|
||||
{ passive: true },
|
||||
);
|
||||
|
||||
document.addEventListener(
|
||||
"touchend",
|
||||
function (e) {
|
||||
if (!touchStartX || !touchStartY) return;
|
||||
|
||||
const touchEndX = e.changedTouches[0].clientX;
|
||||
const touchEndY = e.changedTouches[0].clientY;
|
||||
|
||||
// Check if it's a tap (not a swipe)
|
||||
const diffX = Math.abs(touchEndX - touchStartX);
|
||||
const diffY = Math.abs(touchEndY - touchStartY);
|
||||
|
||||
if (diffX < 10 && diffY < 10) {
|
||||
// This is a tap, ensure immediate response
|
||||
const target = e.target.closest(
|
||||
"a, button, [data-bs-toggle], .product-card, .portfolio-card, .blog-card, .nav-link, .btn",
|
||||
);
|
||||
if (target && !target.disabled) {
|
||||
// Add visual feedback
|
||||
target.style.opacity = "0.7";
|
||||
setTimeout(() => {
|
||||
target.style.opacity = "";
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
touchStartX = null;
|
||||
touchStartY = null;
|
||||
},
|
||||
{ passive: false },
|
||||
);
|
||||
|
||||
// Remove hover states that cause double-tap on mobile
|
||||
if (window.matchMedia("(hover: none)").matches) {
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `
|
||||
/* Override hover states for immediate touch response */
|
||||
.product-card, .portfolio-card, .blog-card, .btn, .nav-link {
|
||||
transition: transform 0.1s ease, opacity 0.1s ease !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
// Optimize click handlers for mobile
|
||||
function optimizeClickHandler(selector) {
|
||||
const elements = document.querySelectorAll(selector);
|
||||
elements.forEach((el) => {
|
||||
if (el.dataset.mobileOptimized) return;
|
||||
el.dataset.mobileOptimized = "true";
|
||||
|
||||
// Remove any existing event listeners that might cause delays
|
||||
el.style.pointerEvents = "auto";
|
||||
el.style.touchAction = "manipulation";
|
||||
|
||||
// Ensure proper z-index for touch
|
||||
const computedStyle = window.getComputedStyle(el);
|
||||
if (computedStyle.position === "static") {
|
||||
el.style.position = "relative";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Apply optimizations when DOM is ready
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
optimizeClickHandler(
|
||||
"a, button, .product-card, .portfolio-card, .blog-card, .btn, .nav-link, .filter-btn, .add-to-cart-btn",
|
||||
);
|
||||
});
|
||||
|
||||
// Apply optimizations to dynamically added content
|
||||
const observer = new MutationObserver(function (mutations) {
|
||||
mutations.forEach(function (mutation) {
|
||||
if (mutation.type === "childList") {
|
||||
mutation.addedNodes.forEach(function (node) {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
optimizeClickHandler(
|
||||
"a, button, .product-card, .portfolio-card, .blog-card, .btn, .nav-link, .filter-btn, .add-to-cart-btn",
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
})();
|
||||
1132
website/public/assets/js/modern-theme.js
Normal file
1132
website/public/assets/js/modern-theme.js
Normal file
File diff suppressed because it is too large
Load Diff
332
website/public/assets/js/shared-utils.js
Normal file
332
website/public/assets/js/shared-utils.js
Normal file
@@ -0,0 +1,332 @@
|
||||
/**
|
||||
* Shared Cart and Wishlist Utilities
|
||||
* Extracted from duplicated code across multiple pages
|
||||
*/
|
||||
|
||||
const CartUtils = {
|
||||
/**
|
||||
* Get cart from localStorage
|
||||
* @returns {Array} Cart items
|
||||
*/
|
||||
getCart() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem("skyart_cart") || "[]");
|
||||
} catch (error) {
|
||||
console.error("Error loading cart:", error);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Save cart to localStorage
|
||||
* @param {Array} cart - Cart items
|
||||
*/
|
||||
saveCart(cart) {
|
||||
try {
|
||||
localStorage.setItem("skyart_cart", JSON.stringify(cart));
|
||||
this.updateCartBadge();
|
||||
} catch (error) {
|
||||
console.error("Error saving cart:", error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add item to cart
|
||||
* @param {Object} product - Product to add
|
||||
* @param {number} quantity - Quantity to add
|
||||
* @param {string} variant - Selected variant
|
||||
*/
|
||||
addToCart(product, quantity = 1, variant = null) {
|
||||
const cart = this.getCart();
|
||||
const cartKey = variant ? `${product.id}-${variant}` : product.id;
|
||||
|
||||
const existingIndex = cart.findIndex((item) => {
|
||||
const itemKey = item.variant
|
||||
? `${item.productId}-${item.variant}`
|
||||
: item.productId;
|
||||
return itemKey === cartKey;
|
||||
});
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
cart[existingIndex].quantity += quantity;
|
||||
} else {
|
||||
cart.push({
|
||||
productId: product.id,
|
||||
name: product.name,
|
||||
price: product.price,
|
||||
image: product.images?.[0]?.image_url || product.imageurl,
|
||||
quantity,
|
||||
variant,
|
||||
slug: product.slug,
|
||||
});
|
||||
}
|
||||
|
||||
this.saveCart(cart);
|
||||
return cart;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove item from cart
|
||||
* @param {string} productId - Product ID to remove
|
||||
* @param {string} variant - Variant to remove
|
||||
*/
|
||||
removeFromCart(productId, variant = null) {
|
||||
const cart = this.getCart();
|
||||
const cartKey = variant ? `${productId}-${variant}` : productId;
|
||||
|
||||
const filtered = cart.filter((item) => {
|
||||
const itemKey = item.variant
|
||||
? `${item.productId}-${item.variant}`
|
||||
: item.productId;
|
||||
return itemKey !== cartKey;
|
||||
});
|
||||
|
||||
this.saveCart(filtered);
|
||||
return filtered;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update cart item quantity
|
||||
* @param {string} productId - Product ID
|
||||
* @param {number} quantity - New quantity
|
||||
* @param {string} variant - Variant
|
||||
*/
|
||||
updateQuantity(productId, quantity, variant = null) {
|
||||
const cart = this.getCart();
|
||||
const cartKey = variant ? `${productId}-${variant}` : productId;
|
||||
|
||||
const index = cart.findIndex((item) => {
|
||||
const itemKey = item.variant
|
||||
? `${item.productId}-${item.variant}`
|
||||
: item.productId;
|
||||
return itemKey === cartKey;
|
||||
});
|
||||
|
||||
if (index >= 0) {
|
||||
if (quantity <= 0) {
|
||||
cart.splice(index, 1);
|
||||
} else {
|
||||
cart[index].quantity = quantity;
|
||||
}
|
||||
this.saveCart(cart);
|
||||
}
|
||||
|
||||
return cart;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get cart total
|
||||
* @returns {number} Total price
|
||||
*/
|
||||
getCartTotal() {
|
||||
const cart = this.getCart();
|
||||
return cart.reduce((total, item) => {
|
||||
return total + parseFloat(item.price) * item.quantity;
|
||||
}, 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update cart badge count
|
||||
*/
|
||||
updateCartBadge() {
|
||||
const cart = this.getCart();
|
||||
const count = cart.reduce((sum, item) => sum + item.quantity, 0);
|
||||
const badge = document.querySelector(".cart-badge");
|
||||
if (badge) {
|
||||
badge.textContent = count;
|
||||
badge.style.display = count > 0 ? "flex" : "none";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear cart
|
||||
*/
|
||||
clearCart() {
|
||||
localStorage.removeItem("skyart_cart");
|
||||
this.updateCartBadge();
|
||||
},
|
||||
};
|
||||
|
||||
const WishlistUtils = {
|
||||
/**
|
||||
* Get wishlist from localStorage
|
||||
* @returns {Array} Wishlist items
|
||||
*/
|
||||
getWishlist() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem("wishlist") || "[]");
|
||||
} catch (error) {
|
||||
console.error("Error loading wishlist:", error);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Save wishlist to localStorage
|
||||
* @param {Array} wishlist - Wishlist items
|
||||
*/
|
||||
saveWishlist(wishlist) {
|
||||
try {
|
||||
localStorage.setItem("wishlist", JSON.stringify(wishlist));
|
||||
this.updateWishlistBadge();
|
||||
} catch (error) {
|
||||
console.error("Error saving wishlist:", error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add item to wishlist
|
||||
* @param {Object} product - Product to add
|
||||
*/
|
||||
addToWishlist(product) {
|
||||
const wishlist = this.getWishlist();
|
||||
|
||||
if (!wishlist.find((item) => item.id === product.id)) {
|
||||
wishlist.push({
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
price: product.price,
|
||||
image: product.images?.[0]?.image_url || product.imageurl,
|
||||
slug: product.slug,
|
||||
});
|
||||
this.saveWishlist(wishlist);
|
||||
}
|
||||
|
||||
return wishlist;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove item from wishlist
|
||||
* @param {string} productId - Product ID to remove
|
||||
*/
|
||||
removeFromWishlist(productId) {
|
||||
const wishlist = this.getWishlist();
|
||||
const filtered = wishlist.filter((item) => item.id !== productId);
|
||||
this.saveWishlist(filtered);
|
||||
return filtered;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if product is in wishlist
|
||||
* @param {string} productId - Product ID
|
||||
* @returns {boolean} True if in wishlist
|
||||
*/
|
||||
isInWishlist(productId) {
|
||||
const wishlist = this.getWishlist();
|
||||
return wishlist.some((item) => item.id === productId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update wishlist badge count
|
||||
*/
|
||||
updateWishlistBadge() {
|
||||
const wishlist = this.getWishlist();
|
||||
const badge = document.querySelector(".wishlist-badge");
|
||||
if (badge) {
|
||||
badge.textContent = wishlist.length;
|
||||
badge.style.display = wishlist.length > 0 ? "flex" : "none";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear wishlist
|
||||
*/
|
||||
clearWishlist() {
|
||||
localStorage.removeItem("wishlist");
|
||||
this.updateWishlistBadge();
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Format price for display
|
||||
* @param {number} price - Price to format
|
||||
* @returns {string} Formatted price
|
||||
*/
|
||||
const formatPrice = (price) => {
|
||||
return `$${parseFloat(price).toFixed(2)}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Format date for display
|
||||
* @param {string} dateString - ISO date string
|
||||
* @returns {string} Formatted date
|
||||
*/
|
||||
const formatDate = (dateString) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Debounce function
|
||||
* @param {Function} func - Function to debounce
|
||||
* @param {number} wait - Wait time in ms
|
||||
* @returns {Function} Debounced function
|
||||
*/
|
||||
const debounce = (func, wait) => {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Show notification toast
|
||||
* @param {string} message - Message to display
|
||||
* @param {string} type - Type: success, error, info
|
||||
*/
|
||||
const showNotification = (message, type = "success") => {
|
||||
// Check if a notification container exists
|
||||
let container = document.querySelector(".notification-container");
|
||||
if (!container) {
|
||||
container = document.createElement("div");
|
||||
container.className = "notification-container";
|
||||
container.style.cssText = `
|
||||
position: fixed;
|
||||
top: 100px;
|
||||
right: 20px;
|
||||
z-index: 10000;
|
||||
`;
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
const notification = document.createElement("div");
|
||||
notification.className = `notification notification-${type}`;
|
||||
notification.style.cssText = `
|
||||
background: ${
|
||||
type === "success" ? "#10b981" : type === "error" ? "#ef4444" : "#3b82f6"
|
||||
};
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
animation: slideIn 0.3s ease-out;
|
||||
`;
|
||||
notification.textContent = message;
|
||||
|
||||
container.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
notification.style.animation = "slideOut 0.3s ease-out";
|
||||
setTimeout(() => notification.remove(), 300);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
// Export for use in other scripts
|
||||
if (typeof window !== "undefined") {
|
||||
window.CartUtils = CartUtils;
|
||||
window.WishlistUtils = WishlistUtils;
|
||||
window.formatPrice = formatPrice;
|
||||
window.formatDate = formatDate;
|
||||
window.debounce = debounce;
|
||||
window.showNotification = showNotification;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
791
website/public/checkout.html
Normal file
791
website/public/checkout.html
Normal file
@@ -0,0 +1,791 @@
|
||||
<!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="Checkout - Sky Art Shop" />
|
||||
<title>Checkout - Sky Art Shop</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<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=Inter:wght@400;500;600;700&family=Poppins:wght@600;700;800&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<!-- Icons -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
/>
|
||||
|
||||
<!-- Modern Theme CSS -->
|
||||
<link rel="stylesheet" href="/assets/css/modern-theme.css?v=20260118drawer" />
|
||||
<link rel="stylesheet" href="/assets/css/mobile-fixes.css?v=20260118c" />
|
||||
|
||||
<style>
|
||||
.checkout-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl) var(--spacing-lg);
|
||||
}
|
||||
|
||||
.checkout-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 400px;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.checkout-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.checkout-section {
|
||||
background: var(--bg-white);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--spacing-xl);
|
||||
box-shadow: var(--shadow-sm);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.checkout-section h2 {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.checkout-section h2 i {
|
||||
color: var(--primary-pink);
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.form-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 1rem;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group select:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-pink);
|
||||
box-shadow: 0 0 0 3px var(--primary-pink-light);
|
||||
}
|
||||
|
||||
.order-summary {
|
||||
position: sticky;
|
||||
top: 100px;
|
||||
}
|
||||
|
||||
.summary-items {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
padding: var(--spacing-sm) 0;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.summary-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.summary-item-image {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: var(--radius-md);
|
||||
object-fit: cover;
|
||||
background: var(--bg-light);
|
||||
}
|
||||
|
||||
.summary-item-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.summary-item-name {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.summary-item-variant {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.summary-item-qty {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.summary-item-price {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.summary-totals {
|
||||
border-top: 2px solid var(--border-light);
|
||||
padding-top: var(--spacing-md);
|
||||
}
|
||||
|
||||
.summary-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.summary-row.total {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-top: var(--spacing-md);
|
||||
padding-top: var(--spacing-md);
|
||||
border-top: 2px solid var(--text-primary);
|
||||
}
|
||||
|
||||
.checkout-btn {
|
||||
width: 100%;
|
||||
padding: var(--spacing-md);
|
||||
font-size: 1.1rem;
|
||||
margin-top: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.empty-cart-message {
|
||||
text-align: center;
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.empty-cart-message i {
|
||||
font-size: 4rem;
|
||||
color: var(--text-light);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.empty-cart-message h3 {
|
||||
margin-bottom: var(--spacing-md);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.payment-methods {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.payment-method {
|
||||
flex: 1;
|
||||
min-width: 150px;
|
||||
padding: var(--spacing-md);
|
||||
border: 2px solid var(--border-light);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.payment-method:hover,
|
||||
.payment-method.active {
|
||||
border-color: var(--primary-pink);
|
||||
background: var(--primary-pink-light);
|
||||
}
|
||||
|
||||
.payment-method i {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.secure-notice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-md);
|
||||
background: var(--bg-light);
|
||||
border-radius: var(--radius-md);
|
||||
margin-top: var(--spacing-lg);
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.secure-notice i {
|
||||
color: var(--success);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Sticky Banner Wrapper -->
|
||||
<div class="sticky-banner-wrapper">
|
||||
<!-- Announcement Bar -->
|
||||
<div class="announcement-bar">
|
||||
<div class="container">
|
||||
<span id="announcement-text"
|
||||
>🌸 Free shipping on orders over $50! Use code: SPRING2026</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar" id="navbar">
|
||||
<div class="container">
|
||||
<a href="/" class="nav-logo">
|
||||
<span class="logo-icon">✿</span>
|
||||
<span>Sky Art Shop</span>
|
||||
</a>
|
||||
|
||||
<button
|
||||
class="mobile-menu-btn"
|
||||
aria-label="Toggle menu"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<i class="bi bi-list"></i>
|
||||
</button>
|
||||
|
||||
<div class="nav-menu">
|
||||
<ul class="nav-links" id="navLinks">
|
||||
<!-- Menu items will be loaded dynamically -->
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="nav-actions">
|
||||
<a href="#" class="nav-action" id="searchToggle" title="Search">
|
||||
<i class="bi bi-search"></i>
|
||||
</a>
|
||||
<a href="#" class="nav-action wishlist-toggle" title="Wishlist">
|
||||
<i class="bi bi-heart"></i>
|
||||
<span class="wishlist-badge">0</span>
|
||||
</a>
|
||||
<a href="#" class="nav-action cart-toggle" title="Cart">
|
||||
<i class="bi bi-bag"></i>
|
||||
<span class="cart-badge">0</span>
|
||||
</a>
|
||||
<a href="#" class="nav-action" id="userMenuToggle" title="Account">
|
||||
<i class="bi bi-person"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Search Overlay -->
|
||||
<div class="search-overlay">
|
||||
<div class="search-container">
|
||||
<form class="search-form" action="/shop" method="get">
|
||||
<input
|
||||
type="search"
|
||||
name="search"
|
||||
placeholder="Search for products..."
|
||||
class="search-input"
|
||||
id="searchInput"
|
||||
/>
|
||||
<button type="submit" class="search-submit">
|
||||
<i class="bi bi-search"></i>
|
||||
</button>
|
||||
</form>
|
||||
<button class="search-close"><i class="bi bi-x"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Page Header -->
|
||||
<section class="page-header" style="padding: var(--spacing-xl) 0">
|
||||
<div class="container text-center">
|
||||
<h1>Checkout</h1>
|
||||
<p class="section-subtitle">Complete your order</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Checkout Content -->
|
||||
<main class="checkout-container">
|
||||
<div id="checkoutContent">
|
||||
<!-- Will be populated by JavaScript based on cart contents -->
|
||||
<div class="checkout-grid">
|
||||
<!-- Left Column - Forms -->
|
||||
<div class="checkout-forms">
|
||||
<!-- Contact Information -->
|
||||
<div class="checkout-section">
|
||||
<h2><i class="bi bi-person-circle"></i> Contact Information</h2>
|
||||
<div class="form-group">
|
||||
<label for="email">Email Address</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
placeholder="your@email.com"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="firstName">First Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="firstName"
|
||||
placeholder="John"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="lastName">Last Name</label>
|
||||
<input type="text" id="lastName" placeholder="Doe" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="phone">Phone Number</label>
|
||||
<input type="tel" id="phone" placeholder="+1 (555) 123-4567" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Shipping Address -->
|
||||
<div class="checkout-section">
|
||||
<h2><i class="bi bi-truck"></i> Shipping Address</h2>
|
||||
<div class="form-group">
|
||||
<label for="address">Street Address</label>
|
||||
<input
|
||||
type="text"
|
||||
id="address"
|
||||
placeholder="123 Creative Lane"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="address2">Apartment, suite, etc. (optional)</label>
|
||||
<input type="text" id="address2" placeholder="Apt 4B" />
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="city">City</label>
|
||||
<input
|
||||
type="text"
|
||||
id="city"
|
||||
placeholder="New York"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="state">State/Province</label>
|
||||
<input type="text" id="state" placeholder="NY" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="zip">ZIP/Postal Code</label>
|
||||
<input type="text" id="zip" placeholder="10001" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="country">Country</label>
|
||||
<select id="country" required>
|
||||
<option value="US">United States</option>
|
||||
<option value="CA">Canada</option>
|
||||
<option value="GB">United Kingdom</option>
|
||||
<option value="AU">Australia</option>
|
||||
<option value="DE">Germany</option>
|
||||
<option value="FR">France</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Payment Method -->
|
||||
<div class="checkout-section">
|
||||
<h2><i class="bi bi-credit-card"></i> Payment Method</h2>
|
||||
<div class="payment-methods">
|
||||
<div class="payment-method active" data-method="card">
|
||||
<i class="bi bi-credit-card-2-front"></i>
|
||||
<span>Credit Card</span>
|
||||
</div>
|
||||
<div class="payment-method" data-method="paypal">
|
||||
<i class="bi bi-paypal"></i>
|
||||
<span>PayPal</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="cardPaymentForm" style="margin-top: var(--spacing-lg)">
|
||||
<div class="form-group">
|
||||
<label for="cardNumber">Card Number</label>
|
||||
<input
|
||||
type="text"
|
||||
id="cardNumber"
|
||||
placeholder="1234 5678 9012 3456"
|
||||
maxlength="19"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="expiry">Expiry Date</label>
|
||||
<input
|
||||
type="text"
|
||||
id="expiry"
|
||||
placeholder="MM/YY"
|
||||
maxlength="5"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cvv">CVV</label>
|
||||
<input
|
||||
type="text"
|
||||
id="cvv"
|
||||
placeholder="123"
|
||||
maxlength="4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cardName">Name on Card</label>
|
||||
<input type="text" id="cardName" placeholder="John Doe" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="secure-notice">
|
||||
<i class="bi bi-shield-lock-fill"></i>
|
||||
<span
|
||||
>Your payment information is encrypted and secure. We never
|
||||
store your full card details.</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column - Order Summary -->
|
||||
<div class="order-summary-wrapper">
|
||||
<div class="checkout-section order-summary">
|
||||
<h2><i class="bi bi-bag-check"></i> Order Summary</h2>
|
||||
<div class="summary-items" id="summaryItems">
|
||||
<!-- Items will be populated by JavaScript -->
|
||||
</div>
|
||||
<div class="summary-totals">
|
||||
<div class="summary-row">
|
||||
<span>Subtotal</span>
|
||||
<span id="summarySubtotal">$0.00</span>
|
||||
</div>
|
||||
<div class="summary-row">
|
||||
<span>Shipping</span>
|
||||
<span id="summaryShipping">Calculated at next step</span>
|
||||
</div>
|
||||
<div class="summary-row">
|
||||
<span>Tax</span>
|
||||
<span id="summaryTax">$0.00</span>
|
||||
</div>
|
||||
<div class="summary-row total">
|
||||
<span>Total</span>
|
||||
<span id="summaryTotal">$0.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary checkout-btn"
|
||||
id="placeOrderBtn"
|
||||
disabled
|
||||
>
|
||||
<i class="bi bi-lock-fill"></i> Place Order
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty Cart State -->
|
||||
<div
|
||||
class="empty-cart-message"
|
||||
id="emptyCartMessage"
|
||||
style="display: none"
|
||||
>
|
||||
<i class="bi bi-cart-x"></i>
|
||||
<h3>Your cart is empty</h3>
|
||||
<p>Add some items to your cart before checking out.</p>
|
||||
<a
|
||||
href="/shop"
|
||||
class="btn btn-primary"
|
||||
style="margin-top: var(--spacing-md)"
|
||||
>
|
||||
<i class="bi bi-arrow-left"></i> Continue Shopping
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-grid">
|
||||
<div class="footer-column">
|
||||
<h4>Sky Art Shop</h4>
|
||||
<p>
|
||||
Your creative journey starts here. Quality supplies for every
|
||||
artist.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="footer-column">
|
||||
<h4>Quick Links</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shop">Shop</a></li>
|
||||
<li><a href="/about">About Us</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-column">
|
||||
<h4>Customer Service</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns</a></li>
|
||||
<li><a href="/privacy">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-column">
|
||||
<h4>Contact Us</h4>
|
||||
<ul class="footer-links">
|
||||
<li><i class="bi bi-envelope"></i> hello@skyartshop.com</li>
|
||||
<li><i class="bi bi-telephone"></i> (555) 123-4567</li>
|
||||
<li><i class="bi bi-geo-alt"></i> 123 Creative Lane</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-bottom">
|
||||
<p>© 2026 Sky Art Shop. All rights reserved.</p>
|
||||
<p>
|
||||
Made with
|
||||
<i class="bi bi-heart-fill" style="color: var(--primary-pink)"></i>
|
||||
for creative souls
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Cart Drawer -->
|
||||
<div class="cart-overlay"></div>
|
||||
<div class="cart-drawer">
|
||||
<div class="cart-header">
|
||||
<h3>Shopping Cart</h3>
|
||||
<button class="cart-close"><i class="bi bi-x"></i></button>
|
||||
</div>
|
||||
<div class="cart-items"></div>
|
||||
<div class="cart-footer">
|
||||
<div class="cart-total">
|
||||
<span>Total:</span>
|
||||
<span class="cart-total-amount">$0.00</span>
|
||||
</div>
|
||||
<a href="/checkout" class="btn btn-primary cart-checkout-btn"
|
||||
>Checkout</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Wishlist Drawer -->
|
||||
<div class="wishlist-overlay"></div>
|
||||
<div class="wishlist-drawer">
|
||||
<div class="wishlist-header">
|
||||
<h3><i class="bi bi-heart"></i> My Wishlist</h3>
|
||||
<button class="wishlist-close"><i class="bi bi-x"></i></button>
|
||||
</div>
|
||||
<div class="wishlist-items"></div>
|
||||
<div class="wishlist-footer">
|
||||
<a href="/shop" class="btn btn-outline">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="/assets/js/modern-theme.js?v=20260118c"></script>
|
||||
<script src="/assets/js/customer-auth.js"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
loadCheckoutPage();
|
||||
setupPaymentMethods();
|
||||
setupFormValidation();
|
||||
});
|
||||
|
||||
function loadCheckoutPage() {
|
||||
const cart = JSON.parse(localStorage.getItem("cart") || "[]");
|
||||
const checkoutGrid = document.querySelector(".checkout-grid");
|
||||
const emptyMessage = document.getElementById("emptyCartMessage");
|
||||
|
||||
if (cart.length === 0) {
|
||||
checkoutGrid.style.display = "none";
|
||||
emptyMessage.style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
checkoutGrid.style.display = "grid";
|
||||
emptyMessage.style.display = "none";
|
||||
|
||||
renderOrderSummary(cart);
|
||||
}
|
||||
|
||||
function renderOrderSummary(cart) {
|
||||
const summaryItems = document.getElementById("summaryItems");
|
||||
let subtotal = 0;
|
||||
|
||||
const itemsHtml = cart
|
||||
.map((item) => {
|
||||
const itemTotal = parseFloat(item.price) * item.quantity;
|
||||
subtotal += itemTotal;
|
||||
|
||||
return `
|
||||
<div class="summary-item">
|
||||
<img
|
||||
src="${
|
||||
item.image || "/assets/images/products/placeholder.jpg"
|
||||
}"
|
||||
alt="${item.name}"
|
||||
class="summary-item-image"
|
||||
onerror="this.src='/assets/images/products/placeholder.jpg'"
|
||||
/>
|
||||
<div class="summary-item-details">
|
||||
<div class="summary-item-name">${item.name}</div>
|
||||
${
|
||||
item.variant
|
||||
? `<div class="summary-item-variant">${item.variant}</div>`
|
||||
: ""
|
||||
}
|
||||
<div class="summary-item-qty">Qty: ${item.quantity}</div>
|
||||
</div>
|
||||
<div class="summary-item-price">$${itemTotal.toFixed(2)}</div>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
summaryItems.innerHTML = itemsHtml;
|
||||
|
||||
// Calculate totals
|
||||
const shipping = subtotal >= 50 ? 0 : 5.99;
|
||||
const tax = subtotal * 0.08; // 8% tax
|
||||
const total = subtotal + shipping + tax;
|
||||
|
||||
document.getElementById(
|
||||
"summarySubtotal"
|
||||
).textContent = `$${subtotal.toFixed(2)}`;
|
||||
document.getElementById("summaryShipping").textContent =
|
||||
shipping === 0 ? "FREE" : `$${shipping.toFixed(2)}`;
|
||||
document.getElementById("summaryTax").textContent = `$${tax.toFixed(
|
||||
2
|
||||
)}`;
|
||||
document.getElementById("summaryTotal").textContent = `$${total.toFixed(
|
||||
2
|
||||
)}`;
|
||||
|
||||
// Enable place order button
|
||||
document.getElementById("placeOrderBtn").disabled = false;
|
||||
}
|
||||
|
||||
function setupPaymentMethods() {
|
||||
const paymentMethods = document.querySelectorAll(".payment-method");
|
||||
const cardForm = document.getElementById("cardPaymentForm");
|
||||
|
||||
paymentMethods.forEach((method) => {
|
||||
method.addEventListener("click", () => {
|
||||
paymentMethods.forEach((m) => m.classList.remove("active"));
|
||||
method.classList.add("active");
|
||||
|
||||
if (method.dataset.method === "card") {
|
||||
cardForm.style.display = "block";
|
||||
} else {
|
||||
cardForm.style.display = "none";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setupFormValidation() {
|
||||
const placeOrderBtn = document.getElementById("placeOrderBtn");
|
||||
|
||||
placeOrderBtn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Basic validation
|
||||
const requiredFields = [
|
||||
"email",
|
||||
"firstName",
|
||||
"lastName",
|
||||
"address",
|
||||
"city",
|
||||
"state",
|
||||
"zip",
|
||||
];
|
||||
let isValid = true;
|
||||
|
||||
requiredFields.forEach((fieldId) => {
|
||||
const field = document.getElementById(fieldId);
|
||||
if (!field.value.trim()) {
|
||||
field.style.borderColor = "var(--error)";
|
||||
isValid = false;
|
||||
} else {
|
||||
field.style.borderColor = "";
|
||||
}
|
||||
});
|
||||
|
||||
if (!isValid) {
|
||||
alert("Please fill in all required fields.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Show success message (in a real app, this would submit to the server)
|
||||
alert(
|
||||
"Thank you for your order! This is a demo - no actual order was placed."
|
||||
);
|
||||
|
||||
// Clear cart
|
||||
localStorage.removeItem("cart");
|
||||
window.location.href = "/";
|
||||
});
|
||||
|
||||
// Format card number with spaces
|
||||
const cardNumberInput = document.getElementById("cardNumber");
|
||||
cardNumberInput.addEventListener("input", (e) => {
|
||||
let value = e.target.value.replace(/\s/g, "").replace(/\D/g, "");
|
||||
value = value.match(/.{1,4}/g)?.join(" ") || value;
|
||||
e.target.value = value;
|
||||
});
|
||||
|
||||
// Format expiry date
|
||||
const expiryInput = document.getElementById("expiry");
|
||||
expiryInput.addEventListener("input", (e) => {
|
||||
let value = e.target.value.replace(/\D/g, "");
|
||||
if (value.length >= 2) {
|
||||
value = value.substring(0, 2) + "/" + value.substring(2);
|
||||
}
|
||||
e.target.value = value;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
265
website/public/faq-old.html
Normal file
265
website/public/faq-old.html
Normal file
@@ -0,0 +1,265 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>FAQ - 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 rel="stylesheet" href="/assets/css/theme-colors.css" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/page-overrides.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/cart-wishlist.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
<link rel="stylesheet" href="/assets/css/responsive.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script>window.__bodyReady=true</script>
|
||||
<div class="sticky-banner-wrapper">
|
||||
<!-- Modern Navigation -->
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home" class="brand-link">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
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" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio" class="nav-link">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog" class="nav-link active">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact" 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" 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"><i class="bi bi-cart-x"></i><br>Your cart is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<a href="/shop" class="btn-outline">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" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact" class="mobile-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mobile-menu-overlay" id="mobileMenuOverlay"></div>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
// Mobile Menu Toggle
|
||||
(function() {
|
||||
const mobileToggle = document.getElementById('mobileMenuToggle');
|
||||
const mobileMenu = document.getElementById('mobileMenu');
|
||||
const mobileClose = document.getElementById('mobileMenuClose');
|
||||
const overlay = document.getElementById('mobileMenuOverlay');
|
||||
|
||||
function openMenu() {
|
||||
mobileMenu.classList.add('active');
|
||||
overlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
function closeMenu() {
|
||||
mobileMenu.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
if (mobileToggle) mobileToggle.addEventListener('click', openMenu);
|
||||
if (mobileClose) mobileClose.addEventListener('click', closeMenu);
|
||||
if (overlay) overlay.addEventListener('click', closeMenu);
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && mobileMenu.classList.contains('active')) closeMenu();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<section class="about-hero">
|
||||
<div class="container">
|
||||
<h1><i class="bi bi-question-circle"></i> Frequently Asked Questions</h1>
|
||||
<p class="hero-subtitle">Quick answers to common questions</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="blog-section" style="padding: 60px 0; background: #ffebeb">
|
||||
<div class="container">
|
||||
<div style="max-width: 800px; margin: 0 auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
|
||||
<div style="margin-bottom: 30px;">
|
||||
<h3 style="color: #667eea;">How long does shipping take?</h3>
|
||||
<p style="color: #666; line-height: 1.8;">Standard shipping takes 5-7 business days. Express shipping (2-3 days) and overnight shipping are also available.</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 30px;">
|
||||
<h3 style="color: #667eea;">Do you ship internationally?</h3>
|
||||
<p style="color: #666; line-height: 1.8;">Yes! We ship to over 50 countries worldwide. International orders typically arrive in 7-14 business days.</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 30px;">
|
||||
<h3 style="color: #667eea;">What is your return policy?</h3>
|
||||
<p style="color: #666; line-height: 1.8;">We offer a 30-day return policy on most items. Items must be unused and in original packaging. See our <a href="/returns" style="color: #667eea;">Returns page</a> for details.</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 30px;">
|
||||
<h3 style="color: #667eea;">How can I track my order?</h3>
|
||||
<p style="color: #666; line-height: 1.8;">Once your order ships, you'll receive a tracking number via email. You can also check your order status in your account dashboard.</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 30px;">
|
||||
<h3 style="color: #667eea;">Do you offer gift wrapping?</h3>
|
||||
<p style="color: #666; line-height: 1.8;">Yes! Add gift wrapping to your order at checkout for just $4.99. We'll include a personalized message card.</p>
|
||||
</div>
|
||||
|
||||
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; border-left: 4px solid #667eea; margin-top: 30px;">
|
||||
<p style="color: #666; margin: 0;"><strong>Still have questions?</strong> Contact us at <a href="mailto:support@skyartshop.com" style="color: #667eea;">support@skyartshop.com</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-grid">
|
||||
<div class="footer-col">
|
||||
<h3 class="footer-title">Sky Art Shop</h3>
|
||||
<p class="footer-text">
|
||||
Your destination for unique art pieces and creative supplies.
|
||||
</p>
|
||||
<div class="social-links">
|
||||
<a href="#" class="social-link"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-instagram"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-twitter"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-pinterest"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Shop</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shop">All Products</a></li>
|
||||
<li><a href="/shop?category=paintings">Paintings</a></li>
|
||||
<li><a href="/shop?category=prints">Prints</a></li>
|
||||
<li><a href="/shop?category=supplies">Art Supplies</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">About</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/about">Our Story</a></li>
|
||||
<li><a href="/portfolio">Portfolio</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Customer Service</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/privacy">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 Sky Art Shop. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Core Scripts (standardized order) -->
|
||||
<script src="/assets/js/main.js"></script>
|
||||
<script src="/assets/js/shop-system.js"></script>
|
||||
<script src="/assets/js/page-transitions.js?v=1766709739"></script>
|
||||
<script src="/assets/js/back-button-control.js?v=1766723554"></script>
|
||||
<script src="/assets/js/navigation.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,265 +1,399 @@
|
||||
<!DOCTYPE html>
|
||||
<!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="Frequently Asked Questions - Sky Art Shop"
|
||||
/>
|
||||
<title>FAQ - Sky Art Shop</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<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"
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@600;700;800&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<!-- Icons -->
|
||||
<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/theme-colors.css" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=1735692100" />
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css?v=1768447584" />
|
||||
<link rel="stylesheet" href="/assets/css/page-overrides.css?v=1736790001" />
|
||||
<link rel="stylesheet" href="/assets/css/cart-wishlist.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
<link rel="stylesheet" href="/assets/css/responsive.css" />
|
||||
|
||||
<!-- Modern Theme CSS -->
|
||||
<link rel="stylesheet" href="/assets/css/modern-theme.css?v=20260118drawer" />
|
||||
<link rel="stylesheet" href="/assets/css/mobile-fixes.css?v=20260118c" />
|
||||
|
||||
<style>
|
||||
.faq-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.faq-item {
|
||||
background: var(--bg-white);
|
||||
border-radius: var(--radius-lg);
|
||||
margin-bottom: var(--spacing-md);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: var(--transition-smooth);
|
||||
}
|
||||
|
||||
.faq-item:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.faq-question {
|
||||
padding: var(--spacing-lg);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.faq-question:hover {
|
||||
background: var(--primary-pink-light);
|
||||
}
|
||||
|
||||
.faq-icon {
|
||||
font-size: 1.2rem;
|
||||
transition: var(--transition-fast);
|
||||
color: var(--primary-pink-dark);
|
||||
}
|
||||
|
||||
.faq-item.active .faq-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.faq-answer {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease;
|
||||
}
|
||||
|
||||
.faq-answer-content {
|
||||
padding: 0 var(--spacing-lg) var(--spacing-lg);
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.faq-item.active .faq-answer {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.contact-box {
|
||||
background: var(--primary-pink-light);
|
||||
padding: var(--spacing-xl);
|
||||
border-radius: var(--radius-lg);
|
||||
text-align: center;
|
||||
margin-top: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.contact-box h3 {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>window.__bodyReady=true</script>
|
||||
<div class="sticky-banner-wrapper">
|
||||
<!-- Modern Navigation -->
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home" class="brand-link">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
alt="Sky Art Shop Logo"
|
||||
class="brand-logo"
|
||||
/>
|
||||
<span class="brand-name">Sky' Art Shop</span>
|
||||
</a>
|
||||
</div>
|
||||
<!-- Navigation -->
|
||||
<header class="nav-wrapper">
|
||||
<nav class="navbar">
|
||||
<a href="/home" class="nav-brand">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
alt="Sky Art Shop Logo"
|
||||
/>
|
||||
<span>Sky Art Shop</span>
|
||||
</a>
|
||||
|
||||
<div class="navbar-menu">
|
||||
<ul class="nav-menu-list">
|
||||
<li class="nav-item">
|
||||
<a href="/home" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio" class="nav-link">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog" class="nav-link active">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact" 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" 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"><i class="bi bi-cart-x"></i><br>Your cart is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<a href="/shop" class="btn-outline">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" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact" class="mobile-link">Contact</a></li>
|
||||
<ul class="nav-menu">
|
||||
<li><a href="/home" class="nav-link">Home</a></li>
|
||||
<li><a href="/shop" class="nav-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="nav-link">Portfolio</a></li>
|
||||
<li><a href="/blog" class="nav-link">Blog</a></li>
|
||||
<li><a href="/about" class="nav-link">About</a></li>
|
||||
<li><a href="/contact" class="nav-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mobile-menu-overlay" id="mobileMenuOverlay"></div>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
// Mobile Menu Toggle
|
||||
(function() {
|
||||
const mobileToggle = document.getElementById('mobileMenuToggle');
|
||||
const mobileMenu = document.getElementById('mobileMenu');
|
||||
const mobileClose = document.getElementById('mobileMenuClose');
|
||||
const overlay = document.getElementById('mobileMenuOverlay');
|
||||
|
||||
function openMenu() {
|
||||
mobileMenu.classList.add('active');
|
||||
overlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
function closeMenu() {
|
||||
mobileMenu.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
if (mobileToggle) mobileToggle.addEventListener('click', openMenu);
|
||||
if (mobileClose) mobileClose.addEventListener('click', closeMenu);
|
||||
if (overlay) overlay.addEventListener('click', closeMenu);
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && mobileMenu.classList.contains('active')) closeMenu();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<section class="about-hero">
|
||||
<div class="container">
|
||||
<h1><i class="bi bi-question-circle"></i> Frequently Asked Questions</h1>
|
||||
<p class="hero-subtitle">Quick answers to common questions</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="blog-section" style="padding: 60px 0; background: #ffebeb">
|
||||
<div class="container">
|
||||
<div style="max-width: 800px; margin: 0 auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
|
||||
<div style="margin-bottom: 30px;">
|
||||
<h3 style="color: #667eea;">How long does shipping take?</h3>
|
||||
<p style="color: #666; line-height: 1.8;">Standard shipping takes 5-7 business days. Express shipping (2-3 days) and overnight shipping are also available.</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 30px;">
|
||||
<h3 style="color: #667eea;">Do you ship internationally?</h3>
|
||||
<p style="color: #666; line-height: 1.8;">Yes! We ship to over 50 countries worldwide. International orders typically arrive in 7-14 business days.</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 30px;">
|
||||
<h3 style="color: #667eea;">What is your return policy?</h3>
|
||||
<p style="color: #666; line-height: 1.8;">We offer a 30-day return policy on most items. Items must be unused and in original packaging. See our <a href="/returns" style="color: #667eea;">Returns page</a> for details.</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 30px;">
|
||||
<h3 style="color: #667eea;">How can I track my order?</h3>
|
||||
<p style="color: #666; line-height: 1.8;">Once your order ships, you'll receive a tracking number via email. You can also check your order status in your account dashboard.</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 30px;">
|
||||
<h3 style="color: #667eea;">Do you offer gift wrapping?</h3>
|
||||
<p style="color: #666; line-height: 1.8;">Yes! Add gift wrapping to your order at checkout for just $4.99. We'll include a personalized message card.</p>
|
||||
</div>
|
||||
|
||||
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; border-left: 4px solid #667eea; margin-top: 30px;">
|
||||
<p style="color: #666; margin: 0;"><strong>Still have questions?</strong> Contact us at <a href="mailto:support@skyartshop.com" style="color: #667eea;">support@skyartshop.com</a></p>
|
||||
<div class="nav-actions">
|
||||
<a href="/signin" class="nav-icon-btn" title="Sign In">
|
||||
<i class="bi bi-person"></i>
|
||||
</a>
|
||||
<button class="nav-icon-btn wishlist-btn-nav" title="Wishlist">
|
||||
<i class="bi bi-heart"></i>
|
||||
<span class="badge wishlist-count" style="display: none">0</span>
|
||||
</button>
|
||||
<button class="nav-icon-btn cart-btn" title="Cart">
|
||||
<i class="bi bi-bag"></i>
|
||||
<span class="badge cart-count" style="display: none">0</span>
|
||||
</button>
|
||||
<button class="nav-mobile-toggle" aria-label="Toggle menu">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- Page Content -->
|
||||
<main class="page-content">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1 id="faqHeaderTitle">
|
||||
<i class="bi bi-question-circle"></i> Frequently Asked Questions
|
||||
</h1>
|
||||
<p id="faqHeaderSubtitle">Quick answers to common questions</p>
|
||||
<div class="breadcrumb">
|
||||
<a href="/home">Home</a>
|
||||
<span>/</span>
|
||||
<span>FAQ</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FAQ Section -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="faq-container" id="faqContainer">
|
||||
<!-- FAQ items will be loaded from API -->
|
||||
<div class="loading-spinner" style="margin: 40px auto"></div>
|
||||
</div>
|
||||
|
||||
<div class="contact-box">
|
||||
<h3>Still have questions?</h3>
|
||||
<p>Our customer service team is here to help!</p>
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
justify-content: center;
|
||||
margin-top: var(--spacing-lg);
|
||||
flex-wrap: wrap;
|
||||
"
|
||||
>
|
||||
<a href="/contact" class="btn btn-primary">Contact Us</a>
|
||||
<a href="mailto:support@skyartshop.com" class="btn btn-outline"
|
||||
>Email Support</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-grid">
|
||||
<div class="footer-col">
|
||||
<h3 class="footer-title">Sky Art Shop</h3>
|
||||
<p class="footer-text">
|
||||
Your destination for unique art pieces and creative supplies.
|
||||
<div class="footer-about">
|
||||
<div class="footer-brand">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
alt="Sky Art Shop"
|
||||
/>
|
||||
<span>Sky Art Shop</span>
|
||||
</div>
|
||||
<p>
|
||||
Your one-stop shop for scrapbooking, journaling, cardmaking, and
|
||||
collaging stationery. Quality products for all your creative
|
||||
needs.
|
||||
</p>
|
||||
<div class="social-links">
|
||||
<div class="footer-social">
|
||||
<a href="#" class="social-link"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-instagram"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-twitter"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-pinterest"></i></a>
|
||||
<a href="#" class="social-link"
|
||||
><i class="bi bi-instagram"></i
|
||||
></a>
|
||||
<a href="#" class="social-link"
|
||||
><i class="bi bi-pinterest"></i
|
||||
></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-youtube"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Shop</h4>
|
||||
|
||||
<div class="footer-column">
|
||||
<h4>Quick Links</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shop">All Products</a></li>
|
||||
<li><a href="/shop?category=paintings">Paintings</a></li>
|
||||
<li><a href="/shop?category=prints">Prints</a></li>
|
||||
<li><a href="/shop?category=supplies">Art Supplies</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">About</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/about">Our Story</a></li>
|
||||
<li><a href="/home">Home</a></li>
|
||||
<li><a href="/shop">Shop</a></li>
|
||||
<li><a href="/portfolio">Portfolio</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/about">About Us</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Customer Service</h4>
|
||||
|
||||
<div class="footer-column">
|
||||
<h4>Customer Service</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns & Refunds</a></li>
|
||||
<li><a href="/privacy">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-column">
|
||||
<h4>Contact Us</h4>
|
||||
<ul class="footer-links">
|
||||
<li><i class="bi bi-envelope"></i> hello@skyartshop.com</li>
|
||||
<li><i class="bi bi-telephone"></i> (555) 123-4567</li>
|
||||
<li><i class="bi bi-geo-alt"></i> 123 Creative Lane</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 Sky Art Shop. All rights reserved.</p>
|
||||
<p>© 2026 Sky Art Shop. All rights reserved.</p>
|
||||
<p>
|
||||
Made with
|
||||
<i class="bi bi-heart-fill" style="color: var(--primary-pink)"></i>
|
||||
for creative souls
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Core Scripts (standardized order) -->
|
||||
<script src="/assets/js/main.js"></script>
|
||||
<script src="/assets/js/shop-system.js"></script>
|
||||
<script src="/assets/js/page-transitions.js?v=1766709739"></script>
|
||||
<script src="/assets/js/back-button-control.js?v=1766723554"></script>
|
||||
<script src="/assets/js/navigation.js"></script>
|
||||
|
||||
|
||||
<!-- Cart Drawer -->
|
||||
<div class="cart-overlay"></div>
|
||||
<div class="cart-drawer">
|
||||
<div class="cart-header">
|
||||
<h3>Shopping Cart</h3>
|
||||
<button class="cart-close"><i class="bi bi-x"></i></button>
|
||||
</div>
|
||||
<div class="cart-items">
|
||||
<!-- Cart items rendered via JavaScript -->
|
||||
</div>
|
||||
<div class="cart-footer">
|
||||
<div class="cart-total">
|
||||
<span>Total:</span>
|
||||
<span class="cart-total-amount">$0.00</span>
|
||||
</div>
|
||||
<a href="/checkout" class="btn btn-primary cart-checkout-btn"
|
||||
>Checkout</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Wishlist Drawer -->
|
||||
<div class="wishlist-overlay"></div>
|
||||
<div class="wishlist-drawer">
|
||||
<div class="wishlist-header">
|
||||
<h3><i class="bi bi-heart"></i> My Wishlist</h3>
|
||||
<button class="wishlist-close"><i class="bi bi-x"></i></button>
|
||||
</div>
|
||||
<div class="wishlist-items"></div>
|
||||
<div class="wishlist-footer">
|
||||
<a href="/shop" class="btn btn-outline">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="/assets/js/modern-theme.js?v=20260118c"></script>
|
||||
<script src="/assets/js/customer-auth.js"></script>
|
||||
<script>
|
||||
// Load FAQ page data from API
|
||||
async function loadFaqPageData() {
|
||||
const container = document.getElementById("faqContainer");
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/pages/faq");
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.page && data.page.pagedata) {
|
||||
const pagedata =
|
||||
typeof data.page.pagedata === "string"
|
||||
? JSON.parse(data.page.pagedata)
|
||||
: data.page.pagedata;
|
||||
|
||||
// Update header
|
||||
if (pagedata.header) {
|
||||
if (pagedata.header.title) {
|
||||
document.getElementById("faqHeaderTitle").innerHTML =
|
||||
'<i class="bi bi-question-circle"></i> ' +
|
||||
pagedata.header.title;
|
||||
}
|
||||
if (pagedata.header.subtitle) {
|
||||
document.getElementById("faqHeaderSubtitle").textContent =
|
||||
pagedata.header.subtitle;
|
||||
}
|
||||
}
|
||||
|
||||
// Render FAQ items
|
||||
if (pagedata.items && pagedata.items.length > 0) {
|
||||
container.innerHTML = pagedata.items
|
||||
.map(
|
||||
(item, index) => `
|
||||
<div class="faq-item ${index === 0 ? "active" : ""}">
|
||||
<div class="faq-question">
|
||||
<span>${escapeHtml(item.question)}</span>
|
||||
<i class="bi bi-chevron-down faq-icon"></i>
|
||||
</div>
|
||||
<div class="faq-answer" ${index === 0 ? 'style="max-height: 500px;"' : ""}>
|
||||
<div class="faq-answer-content">
|
||||
<p>${item.answer}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
|
||||
// Setup accordion behavior
|
||||
setupFaqAccordion();
|
||||
} else {
|
||||
container.innerHTML =
|
||||
'<p class="text-center">No FAQs available yet.</p>';
|
||||
}
|
||||
} else {
|
||||
container.innerHTML =
|
||||
'<p class="text-center">Unable to load FAQs.</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading FAQ page:", error);
|
||||
container.innerHTML =
|
||||
'<p class="text-center">Unable to load FAQs.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
function setupFaqAccordion() {
|
||||
document.querySelectorAll(".faq-item").forEach((item) => {
|
||||
const question = item.querySelector(".faq-question");
|
||||
question.addEventListener("click", () => {
|
||||
const isActive = item.classList.contains("active");
|
||||
|
||||
// Close all items
|
||||
document.querySelectorAll(".faq-item").forEach((i) => {
|
||||
i.classList.remove("active");
|
||||
i.querySelector(".faq-answer").style.maxHeight = "0";
|
||||
});
|
||||
|
||||
// Open clicked item if it wasn't active
|
||||
if (!isActive) {
|
||||
item.classList.add("active");
|
||||
item.querySelector(".faq-answer").style.maxHeight = "500px";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
if (!text) return "";
|
||||
const div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Load page data on DOMContentLoaded
|
||||
document.addEventListener("DOMContentLoaded", loadFaqPageData);
|
||||
</script>
|
||||
<script src="/assets/js/accessibility.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
128
website/public/logo-preview.html
Normal file
128
website/public/logo-preview.html
Normal file
@@ -0,0 +1,128 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Cat Logo Preview</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Inter", sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
text-align: center;
|
||||
max-width: 600px;
|
||||
}
|
||||
h1 {
|
||||
color: #2d3436;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
p {
|
||||
color: #636e72;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.logo-display {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin: 30px 0;
|
||||
}
|
||||
.logo-box {
|
||||
padding: 30px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 15px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.logo-box:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.logo-box img {
|
||||
display: block;
|
||||
}
|
||||
.size-label {
|
||||
margin-top: 15px;
|
||||
font-size: 14px;
|
||||
color: #636e72;
|
||||
font-weight: 500;
|
||||
}
|
||||
.info {
|
||||
background: #fec6df;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.info h3 {
|
||||
color: #c239b3;
|
||||
margin-top: 0;
|
||||
}
|
||||
.info code {
|
||||
background: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
color: #c239b3;
|
||||
font-family: "Courier New", monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🐱 Sky Art Shop Cat Logo</h1>
|
||||
<p>Your creative companion logo is ready!</p>
|
||||
|
||||
<div class="logo-display">
|
||||
<div class="logo-box">
|
||||
<img
|
||||
src="/assets/images/logo/cat-logo.svg"
|
||||
width="100"
|
||||
height="100"
|
||||
alt="Cat Logo - Large"
|
||||
/>
|
||||
<div class="size-label">Large (100px)</div>
|
||||
</div>
|
||||
<div class="logo-box">
|
||||
<img
|
||||
src="/assets/images/logo/cat-logo.svg"
|
||||
width="60"
|
||||
height="60"
|
||||
alt="Cat Logo - Medium"
|
||||
/>
|
||||
<div class="size-label">Medium (60px)</div>
|
||||
</div>
|
||||
<div class="logo-box">
|
||||
<img
|
||||
src="/assets/images/logo/cat-logo.svg"
|
||||
width="40"
|
||||
height="40"
|
||||
alt="Cat Logo - Small"
|
||||
/>
|
||||
<div class="size-label">Small (40px)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
<h3>✨ Logo Applied to About Page</h3>
|
||||
<p style="color: #2d3436; margin: 10px 0">
|
||||
The cat logo now appears next to "Our Story" section on the About
|
||||
page!
|
||||
</p>
|
||||
<p style="color: #636e72; font-size: 14px; margin: 5px 0">
|
||||
Location: <code>/assets/images/logo/cat-logo.svg</code>
|
||||
</p>
|
||||
<p style="color: #636e72; font-size: 14px; margin: 5px 0">
|
||||
<strong>View it:</strong>
|
||||
<a href="/about" style="color: #c239b3">Go to About Page →</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
434
website/public/old-backup/about.html
Normal file
434
website/public/old-backup/about.html
Normal file
@@ -0,0 +1,434 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- Security Headers -->
|
||||
<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 rel="stylesheet" href="/assets/css/theme-colors.css" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/page-overrides.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/cart-wishlist.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
<link rel="stylesheet" href="/assets/css/responsive.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.__bodyReady = true;
|
||||
</script>
|
||||
<!-- Modern Navigation -->
|
||||
<div class="sticky-banner-wrapper">
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home" class="brand-link">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
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" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio" class="nav-link">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about" class="nav-link active">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog" class="nav-link">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact" 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" 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">
|
||||
<a href="/shop" class="btn-outline">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" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact" class="mobile-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<section class="about-hero">
|
||||
<div class="container">
|
||||
<h1>About Sky Art Shop</h1>
|
||||
<p class="hero-subtitle">Your creative journey starts here</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="about-content">
|
||||
<div class="container">
|
||||
<div class="about-layout">
|
||||
<div class="about-main-content">
|
||||
<div class="about-text" id="aboutContent">
|
||||
<div style="text-align: center; padding: 40px">
|
||||
<div
|
||||
class="loading-spinner"
|
||||
style="
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #667eea;
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20px;
|
||||
"
|
||||
></div>
|
||||
<p>Loading content...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Team Members Section -->
|
||||
<section class="team-section" id="teamSection" style="display: none">
|
||||
<div class="container">
|
||||
<div class="team-header">
|
||||
<h2 class="section-title">Meet Our Team</h2>
|
||||
<p class="section-subtitle">
|
||||
The talented people behind Sky Art Shop
|
||||
</p>
|
||||
</div>
|
||||
<div class="team-grid" id="teamMembersGrid">
|
||||
<div style="text-align: center; padding: 40px">
|
||||
<div
|
||||
class="loading-spinner"
|
||||
style="
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #667eea;
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20px;
|
||||
"
|
||||
></div>
|
||||
<p>Loading team...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-grid">
|
||||
<div class="footer-col">
|
||||
<h3 class="footer-title">Sky Art Shop</h3>
|
||||
<p class="footer-text">
|
||||
Your destination for unique art pieces and creative supplies.
|
||||
</p>
|
||||
<div class="social-links">
|
||||
<a href="#" class="social-link"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#" class="social-link"
|
||||
><i class="bi bi-instagram"></i
|
||||
></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-twitter"></i></a>
|
||||
<a href="#" class="social-link"
|
||||
><i class="bi bi-pinterest"></i
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Shop</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shop">All Products</a></li>
|
||||
<li><a href="/shop?category=paintings">Paintings</a></li>
|
||||
<li><a href="/shop?category=prints">Prints</a></li>
|
||||
<li><a href="/shop?category=supplies">Art Supplies</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">About</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/about">Our Story</a></li>
|
||||
<li><a href="/portfolio">Portfolio</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Customer Service</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/privacy">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 Sky Art Shop. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Core utilities and state management -->
|
||||
<script src="/assets/js/api-cache.js"></script>
|
||||
<script src="/assets/js/main.js"></script>
|
||||
|
||||
<!-- Shopping system (handles cart & wishlist) -->
|
||||
<script src="/assets/js/shop-system.js"></script>
|
||||
|
||||
<!-- Page interactions -->
|
||||
<script src="/assets/js/page-transitions.js?v=1766709739"></script>
|
||||
<script src="/assets/js/back-button-control.js?v=1766723554"></script>
|
||||
<script src="/assets/js/navigation.js"></script>
|
||||
|
||||
<!-- Page-specific content loading -->
|
||||
<script>
|
||||
// Load about page content from API
|
||||
async function loadAboutContent() {
|
||||
try {
|
||||
const response = await fetch("/api/pages/about");
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.page) {
|
||||
const contentDiv = document.getElementById("aboutContent");
|
||||
|
||||
// Check if content is Quill Delta format (JSON)
|
||||
if (data.page.content) {
|
||||
try {
|
||||
const delta = JSON.parse(data.page.content);
|
||||
// Convert Delta to HTML
|
||||
contentDiv.innerHTML = convertDeltaToHTML(delta);
|
||||
} catch {
|
||||
// If not JSON, treat as plain HTML
|
||||
contentDiv.innerHTML = data.page.content;
|
||||
}
|
||||
} else {
|
||||
contentDiv.innerHTML = "<p>Content not available.</p>";
|
||||
}
|
||||
|
||||
// Update meta tags if available
|
||||
if (data.page.metatitle) {
|
||||
document.title = data.page.metatitle;
|
||||
}
|
||||
if (data.page.metadescription) {
|
||||
const metaDesc = document.querySelector(
|
||||
'meta[name="description"]'
|
||||
);
|
||||
if (metaDesc) {
|
||||
metaDesc.content = data.page.metadescription;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
document.getElementById("aboutContent").innerHTML =
|
||||
"<p>Unable to load content.</p>";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading about content:", error);
|
||||
document.getElementById("aboutContent").innerHTML =
|
||||
"<p>Error loading content.</p>";
|
||||
}
|
||||
}
|
||||
|
||||
// Convert Quill Delta to HTML
|
||||
function convertDeltaToHTML(delta) {
|
||||
if (!delta || !delta.ops) return "";
|
||||
|
||||
let html = "";
|
||||
let currentBlock = "";
|
||||
|
||||
delta.ops.forEach((op) => {
|
||||
if (typeof op.insert === "string") {
|
||||
let text = op.insert;
|
||||
|
||||
// Apply text formatting
|
||||
if (op.attributes) {
|
||||
if (op.attributes.bold) text = `<strong>${text}</strong>`;
|
||||
if (op.attributes.italic) text = `<em>${text}</em>`;
|
||||
if (op.attributes.underline) text = `<u>${text}</u>`;
|
||||
if (op.attributes.strike) text = `<s>${text}</s>`;
|
||||
if (op.attributes.code) text = `<code>${text}</code>`;
|
||||
if (op.attributes.link)
|
||||
text = `<a href="${op.attributes.link}" target="_blank">${text}</a>`;
|
||||
if (op.attributes.color)
|
||||
text = `<span style="color: ${op.attributes.color}">${text}</span>`;
|
||||
if (op.attributes.background)
|
||||
text = `<span style="background-color: ${op.attributes.background}">${text}</span>`;
|
||||
}
|
||||
|
||||
// Handle line breaks
|
||||
const lines = text.split("\n");
|
||||
lines.forEach((line, index) => {
|
||||
currentBlock += line;
|
||||
if (index < lines.length - 1) {
|
||||
// New paragraph
|
||||
if (currentBlock.trim()) {
|
||||
html += `<p>${currentBlock}</p>`;
|
||||
}
|
||||
currentBlock = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add remaining content
|
||||
if (currentBlock.trim()) {
|
||||
html += `<p>${currentBlock}</p>`;
|
||||
}
|
||||
|
||||
return html || "<p>Content not available.</p>";
|
||||
}
|
||||
|
||||
// Load team members
|
||||
async function loadTeamMembers() {
|
||||
try {
|
||||
const response = await fetch("/api/team-members");
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.teamMembers && data.teamMembers.length > 0) {
|
||||
displayTeamMembers(data.teamMembers);
|
||||
document.getElementById("teamSection").style.display = "block";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading team members:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Display team members
|
||||
function displayTeamMembers(members) {
|
||||
const grid = document.getElementById("teamMembersGrid");
|
||||
|
||||
grid.innerHTML = members
|
||||
.map(
|
||||
(member) => `
|
||||
<div class="team-card">
|
||||
<div class="team-image-wrapper">
|
||||
<div class="team-image">
|
||||
${
|
||||
member.image_url
|
||||
? `<img src="${member.image_url}" alt="${escapeHtml(
|
||||
member.name
|
||||
)}" />`
|
||||
: `<i class="bi bi-person-circle"></i>`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="team-name">${escapeHtml(member.name)}</h3>
|
||||
<div class="team-position">${escapeHtml(member.position)}</div>
|
||||
${
|
||||
member.bio
|
||||
? `<p class="team-bio">${escapeHtml(member.bio)}</p>`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
// Escape HTML to prevent XSS
|
||||
function escapeHtml(text) {
|
||||
if (!text) return "";
|
||||
const div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Load content when page loads
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
loadAboutContent();
|
||||
loadTeamMembers();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
329
website/public/old-backup/blog.html
Normal file
329
website/public/old-backup/blog.html
Normal file
@@ -0,0 +1,329 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- Security Headers -->
|
||||
<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 rel="stylesheet" href="/assets/css/theme-colors.css" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/page-overrides.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/cart-wishlist.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
<link rel="stylesheet" href="/assets/css/responsive.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script>window.__bodyReady=true</script>
|
||||
<div class="sticky-banner-wrapper">
|
||||
<!-- Modern Navigation -->
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home" class="brand-link">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
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" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio" class="nav-link">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog" class="nav-link active">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact" 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" 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"><i class="bi bi-cart-x"></i><br>Your cart is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<a href="/shop" class="btn-outline">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" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact" class="mobile-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mobile-menu-overlay" id="mobileMenuOverlay"></div>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
// Mobile Menu Toggle
|
||||
(function() {
|
||||
const mobileToggle = document.getElementById('mobileMenuToggle');
|
||||
const mobileMenu = document.getElementById('mobileMenu');
|
||||
const mobileClose = document.getElementById('mobileMenuClose');
|
||||
const overlay = document.getElementById('mobileMenuOverlay');
|
||||
|
||||
function openMenu() {
|
||||
mobileMenu.classList.add('active');
|
||||
overlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
function closeMenu() {
|
||||
mobileMenu.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
if (mobileToggle) mobileToggle.addEventListener('click', openMenu);
|
||||
if (mobileClose) mobileClose.addEventListener('click', closeMenu);
|
||||
if (overlay) overlay.addEventListener('click', closeMenu);
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && mobileMenu.classList.contains('active')) closeMenu();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<section class="about-hero">
|
||||
<div class="container">
|
||||
<h1>Blog</h1>
|
||||
<p class="hero-subtitle">Inspiration, tips, and creative ideas</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="blog-section" style="padding: 60px 0; background: #ffebeb">
|
||||
<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-grid">
|
||||
<div class="footer-col">
|
||||
<h3 class="footer-title">Sky Art Shop</h3>
|
||||
<p class="footer-text">
|
||||
Your destination for unique art pieces and creative supplies.
|
||||
</p>
|
||||
<div class="social-links">
|
||||
<a href="#" class="social-link"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-instagram"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-twitter"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-pinterest"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Shop</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shop">All Products</a></li>
|
||||
<li><a href="/shop?category=paintings">Paintings</a></li>
|
||||
<li><a href="/shop?category=prints">Prints</a></li>
|
||||
<li><a href="/shop?category=supplies">Art Supplies</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">About</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/about">Our Story</a></li>
|
||||
<li><a href="/portfolio">Portfolio</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Customer Service</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/privacy">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 Sky Art Shop. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Core dependencies (load in order) -->
|
||||
<script src="/assets/js/api-cache.js"></script>
|
||||
<script src="/assets/js/main.js"></script>
|
||||
<script src="/assets/js/shop-system.js"></script>
|
||||
|
||||
<!-- UI enhancements -->
|
||||
<script src="/assets/js/page-transitions.js?v=1766709739"></script>
|
||||
<script src="/assets/js/back-button-control.js?v=1766723554"></script>
|
||||
<script src="/assets/js/navigation.js"></script>
|
||||
|
||||
<!-- Page-specific functionality -->
|
||||
<script>
|
||||
// Load blog posts from API
|
||||
async function loadBlog() {
|
||||
try {
|
||||
const response = await window.apiCache.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> ${post.createdat && Object.keys(post.createdat).length > 0 ? new Date(post.createdat).toLocaleDateString() : 'Recent'}</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>
|
||||
</article>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
} else {
|
||||
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>
|
||||
642
website/public/old-backup/contact.html
Normal file
642
website/public/old-backup/contact.html
Normal file
@@ -0,0 +1,642 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- Security Headers -->
|
||||
<title>Contact Us - 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 rel="stylesheet" href="/assets/css/theme-colors.css" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/page-overrides.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/cart-wishlist.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
<link rel="stylesheet" href="/assets/css/responsive.css" />
|
||||
<link rel="stylesheet" href="/assets/css/theme-colors.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.__bodyReady = true;
|
||||
</script>
|
||||
<!-- Modern Navigation -->
|
||||
<div class="sticky-banner-wrapper">
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home" class="brand-link">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
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" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio" class="nav-link">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog" class="nav-link">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact" class="nav-link active">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" 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">
|
||||
<a href="/shop" class="btn-outline">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" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact" class="mobile-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Contact Hero -->
|
||||
<section
|
||||
class="contact-hero"
|
||||
style="
|
||||
background: linear-gradient(135deg, #f6ccde 0%, #fcb1d8 100%);
|
||||
padding: 40px 0 30px;
|
||||
color: #202023;
|
||||
text-align: center;
|
||||
"
|
||||
>
|
||||
<div class="container">
|
||||
<h1
|
||||
style="
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 700;
|
||||
color: #202023;
|
||||
"
|
||||
>
|
||||
Get In Touch
|
||||
</h1>
|
||||
<p
|
||||
style="
|
||||
font-size: 1.1rem;
|
||||
color: #202023;
|
||||
opacity: 0.9;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
"
|
||||
>
|
||||
Have questions or feedback? We'd love to hear from you. Send us a
|
||||
message and we'll respond as soon as possible.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Business Contact Information -->
|
||||
<section
|
||||
style="padding: 60px 0 40px; background: white"
|
||||
id="contactInfoSection"
|
||||
>
|
||||
<div class="container" style="max-width: 1000px">
|
||||
<div style="text-align: center; padding: 40px">
|
||||
<div
|
||||
class="loading-spinner"
|
||||
style="
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #fcb1d8;
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20px;
|
||||
"
|
||||
></div>
|
||||
<p>Loading contact information...</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Contact Form Section -->
|
||||
<section
|
||||
class="contact-section"
|
||||
style="padding: 80px 0; background: #f8f9fa"
|
||||
>
|
||||
<div class="container" style="max-width: 800px">
|
||||
<div style="text-align: center; margin-bottom: 40px">
|
||||
<h2
|
||||
style="
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: #202023;
|
||||
margin-bottom: 12px;
|
||||
"
|
||||
>
|
||||
Send Us a Message
|
||||
</h2>
|
||||
<p style="font-size: 1rem; color: #202023">
|
||||
Fill out the form below and we'll get back to you within 24 hours
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="contact-form-wrapper"
|
||||
style="
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 48px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
"
|
||||
>
|
||||
<form
|
||||
id="contactForm"
|
||||
style="display: flex; flex-direction: column; gap: 16px"
|
||||
>
|
||||
<!-- Name and Email Row -->
|
||||
<div
|
||||
style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px"
|
||||
>
|
||||
<!-- Name Field -->
|
||||
<div class="form-group">
|
||||
<label
|
||||
for="name"
|
||||
style="
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
color: #2d3436;
|
||||
font-size: 15px;
|
||||
"
|
||||
>
|
||||
Full Name <span style="color: #ff6b6b">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
required
|
||||
placeholder="John Doe"
|
||||
style="
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
border: 2px solid #e1e8ed;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
transition: all 0.3s;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
"
|
||||
onfocus="this.style.borderColor='#FCB1D8'; this.style.outline='none';"
|
||||
onblur="this.style.borderColor='#e1e8ed';"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Email Field -->
|
||||
<div class="form-group">
|
||||
<label
|
||||
for="email"
|
||||
style="
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
color: #202023;
|
||||
font-size: 15px;
|
||||
"
|
||||
>
|
||||
Email Address <span style="color: #fcb1d8">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
placeholder="john@example.com"
|
||||
style="
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
border: 2px solid #e1e8ed;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
transition: all 0.3s;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
"
|
||||
onfocus="this.style.borderColor='#FCB1D8'; this.style.outline='none';"
|
||||
onblur="this.style.borderColor='#e1e8ed';"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Phone and Subject Row -->
|
||||
<div
|
||||
style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px"
|
||||
>
|
||||
<!-- Phone Field -->
|
||||
<div class="form-group">
|
||||
<label
|
||||
for="phone"
|
||||
style="
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
color: #202023;
|
||||
font-size: 15px;
|
||||
"
|
||||
>
|
||||
Phone Number
|
||||
<span style="color: #202023; font-weight: 400; opacity: 0.6"
|
||||
>(Optional)</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="phone"
|
||||
name="phone"
|
||||
placeholder="(123) 456-7890"
|
||||
style="
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
border: 2px solid #e1e8ed;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
transition: all 0.3s;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
"
|
||||
onfocus="this.style.borderColor='#FCB1D8'; this.style.outline='none';"
|
||||
onblur="this.style.borderColor='#e1e8ed';"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Subject Field -->
|
||||
<div class="form-group">
|
||||
<label
|
||||
for="subject"
|
||||
style="
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
color: #2d3436;
|
||||
font-size: 15px;
|
||||
"
|
||||
>
|
||||
Subject <span style="color: #ff6b6b">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="subject"
|
||||
name="subject"
|
||||
required
|
||||
placeholder="How can we help you?"
|
||||
style="
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
border: 2px solid #e1e8ed;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
transition: all 0.3s;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
"
|
||||
onfocus="this.style.borderColor='#FCB1D8'; this.style.outline='none';"
|
||||
onblur="this.style.borderColor='#e1e8ed';"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message Field -->
|
||||
<div class="form-group">
|
||||
<label
|
||||
for="message"
|
||||
style="
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
color: #202023;
|
||||
font-size: 15px;
|
||||
"
|
||||
>
|
||||
Full Name <span style="color: #fcb1d8">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
required
|
||||
rows="6"
|
||||
placeholder="Tell us more about your inquiry..."
|
||||
style="
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
border: 2px solid #e1e8ed;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
transition: all 0.3s;
|
||||
resize: vertical;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
line-height: 1.6;
|
||||
"
|
||||
onfocus="this.style.borderColor='#FCB1D8'; this.style.outline='none';"
|
||||
onblur="this.style.borderColor='#e1e8ed';"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div style="margin-top: 8px">
|
||||
<button
|
||||
type="submit"
|
||||
style="
|
||||
width: 100%;
|
||||
padding: 16px 32px;
|
||||
background: linear-gradient(135deg, #f6ccde 0%, #fcb1d8 100%);
|
||||
color: #202023;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
"
|
||||
onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 8px 20px rgba(252,177,216,0.4)';"
|
||||
onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='none';"
|
||||
>
|
||||
<i class="bi bi-send" style="margin-right: 8px"></i>
|
||||
Send Message
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Success/Error Message -->
|
||||
<div
|
||||
id="formMessage"
|
||||
style="
|
||||
display: none;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
"
|
||||
></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-grid">
|
||||
<div class="footer-col">
|
||||
<h3 class="footer-title">Sky Art Shop</h3>
|
||||
<p class="footer-text">
|
||||
Your destination for unique art pieces and creative supplies.
|
||||
</p>
|
||||
<div class="social-links">
|
||||
<a href="#" class="social-link"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#" class="social-link"
|
||||
><i class="bi bi-instagram"></i
|
||||
></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-twitter"></i></a>
|
||||
<a href="#" class="social-link"
|
||||
><i class="bi bi-pinterest"></i
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Shop</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shop">All Products</a></li>
|
||||
<li><a href="/shop?category=paintings">Paintings</a></li>
|
||||
<li><a href="/shop?category=prints">Prints</a></li>
|
||||
<li><a href="/shop?category=supplies">Art Supplies</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">About</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/about">Our Story</a></li>
|
||||
<li><a href="/portfolio">Portfolio</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Customer Service</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/privacy">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 Sky Art Shop. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Core utilities and state management -->
|
||||
<script src="/assets/js/api-cache.js"></script>
|
||||
<script src="/assets/js/main.js"></script>
|
||||
|
||||
<!-- Shopping system (handles cart & wishlist) -->
|
||||
<script src="/assets/js/shop-system.js"></script>
|
||||
|
||||
<!-- Page interactions -->
|
||||
<script src="/assets/js/page-transitions.js?v=1766709739"></script>
|
||||
<script src="/assets/js/back-button-control.js?v=1766723554"></script>
|
||||
<script src="/assets/js/navigation.js"></script>
|
||||
|
||||
<!-- Page-specific functionality -->
|
||||
<script>
|
||||
// Handle contact form submission
|
||||
document
|
||||
.getElementById("contactForm")
|
||||
.addEventListener("submit", async function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formMessage = document.getElementById("formMessage");
|
||||
const submitButton = e.target.querySelector('button[type="submit"]');
|
||||
const originalButtonText = submitButton.innerHTML;
|
||||
|
||||
// Disable button and show loading state
|
||||
submitButton.disabled = true;
|
||||
submitButton.innerHTML =
|
||||
'<i class="bi bi-hourglass-split" style="margin-right: 8px;"></i>Sending...';
|
||||
|
||||
// Get form data
|
||||
const formData = {
|
||||
name: document.getElementById("name").value,
|
||||
email: document.getElementById("email").value,
|
||||
phone: document.getElementById("phone").value,
|
||||
subject: document.getElementById("subject").value,
|
||||
message: document.getElementById("message").value,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
try {
|
||||
// Simulate API call (replace with actual endpoint when ready)
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
|
||||
// Show success message
|
||||
formMessage.style.display = "block";
|
||||
formMessage.style.background = "#d4edda";
|
||||
formMessage.style.color = "#155724";
|
||||
formMessage.style.border = "1px solid #c3e6cb";
|
||||
formMessage.innerHTML =
|
||||
'<i class="bi bi-check-circle" style="margin-right: 8px;"></i>Thank you! Your message has been sent successfully. We\'ll get back to you soon.';
|
||||
|
||||
// Reset form
|
||||
e.target.reset();
|
||||
|
||||
// Log to console (for now)
|
||||
console.log("Contact form submitted:", formData);
|
||||
} catch (error) {
|
||||
// Show error message
|
||||
formMessage.style.display = "block";
|
||||
formMessage.style.background = "#f8d7da";
|
||||
formMessage.style.color = "#721c24";
|
||||
formMessage.style.border = "1px solid #f5c6cb";
|
||||
formMessage.innerHTML =
|
||||
'<i class="bi bi-exclamation-circle" style="margin-right: 8px;"></i>Oops! Something went wrong. Please try again or email us directly.';
|
||||
|
||||
console.error("Contact form error:", error);
|
||||
} finally {
|
||||
// Re-enable button
|
||||
submitButton.disabled = false;
|
||||
submitButton.innerHTML = originalButtonText;
|
||||
|
||||
// Hide message after 5 seconds
|
||||
setTimeout(() => {
|
||||
formMessage.style.display = "none";
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
// Load contact information from API
|
||||
async function loadContactInfo() {
|
||||
try {
|
||||
const response = await fetch("/api/pages/contact");
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.page) {
|
||||
const section = document.getElementById("contactInfoSection");
|
||||
section.innerHTML = `
|
||||
<div class="container" style="max-width: 1000px">
|
||||
${data.page.content}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Update meta tags
|
||||
if (data.page.metatitle) {
|
||||
document.title = data.page.metatitle;
|
||||
}
|
||||
if (data.page.metadescription) {
|
||||
const metaDesc = document.querySelector(
|
||||
'meta[name="description"]'
|
||||
);
|
||||
if (metaDesc) {
|
||||
metaDesc.content = data.page.metadescription;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading contact info:", error);
|
||||
document.getElementById("contactInfoSection").innerHTML =
|
||||
'<div class="container"><p style="text-align:center;">Error loading contact information.</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Load content when page loads
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", loadContactInfo);
|
||||
} else {
|
||||
loadContactInfo();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
625
website/public/old-backup/home.html
Normal file
625
website/public/old-backup/home.html
Normal file
@@ -0,0 +1,625 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- Security Headers -->
|
||||
<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/theme-colors.css" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/page-overrides.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/cart-wishlist.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
<link rel="stylesheet" href="/assets/css/responsive.css" />
|
||||
<style>
|
||||
/* Body Reset */
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #ffebeb;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Sticky Banner Container */
|
||||
.sticky-banner-wrapper {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
background: #ffd0d0;
|
||||
}
|
||||
|
||||
/* Override navbar position when inside sticky wrapper */
|
||||
.sticky-banner-wrapper .modern-navbar {
|
||||
position: relative;
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>window.__bodyReady=true</script>
|
||||
<!-- Sticky Banner Wrapper -->
|
||||
<div class="sticky-banner-wrapper">
|
||||
<!-- Modern Navigation -->
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home" class="brand-link">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
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" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio" class="nav-link">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog" class="nav-link">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact" 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" 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"><i class="bi bi-cart-x"></i><br>Your cart is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<a href="/shop" class="btn-outline">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" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact" class="mobile-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mobile-menu-overlay" id="mobileMenuOverlay"></div>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
// Mobile Menu Toggle
|
||||
(function() {
|
||||
const mobileToggle = document.getElementById('mobileMenuToggle');
|
||||
const mobileMenu = document.getElementById('mobileMenu');
|
||||
const mobileClose = document.getElementById('mobileMenuClose');
|
||||
const overlay = document.getElementById('mobileMenuOverlay');
|
||||
|
||||
function openMenu() {
|
||||
mobileMenu.classList.add('active');
|
||||
overlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
mobileMenu.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
if (mobileToggle) mobileToggle.addEventListener('click', openMenu);
|
||||
if (mobileClose) mobileClose.addEventListener('click', closeMenu);
|
||||
if (overlay) overlay.addEventListener('click', closeMenu);
|
||||
|
||||
// Close on ESC key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && mobileMenu.classList.contains('active')) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<section class="hero" id="heroSection">
|
||||
<div class="hero-content" id="heroContent">
|
||||
<h2 id="heroHeadline">Welcome to Sky Art Shop</h2>
|
||||
<p id="heroSubheading">
|
||||
Your destination for creative stationery and supplies
|
||||
</p>
|
||||
<div class="hero-description" id="heroDescription">
|
||||
<p>
|
||||
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" class="btn btn-primary" id="heroCtaBtn">Shop Now</a>
|
||||
</div>
|
||||
<div class="hero-image" id="heroImageContainer">
|
||||
<img
|
||||
src="/assets/images/hero-image.jpg"
|
||||
alt="Sky Art Shop"
|
||||
loading="lazy"
|
||||
onerror="this.style.display='none'"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Promotion/Inspiration Section -->
|
||||
<section class="inspiration" id="promotionSection">
|
||||
<div class="container">
|
||||
<h2 id="promotionTitle">Get Inspired</h2>
|
||||
<div class="inspiration-content" id="promotionContent">
|
||||
<div class="inspiration-text" id="promotionText">
|
||||
<p>
|
||||
At Sky Art Shop, we believe in the power of creativity to
|
||||
transform and inspire. Whether you're an experienced crafter or
|
||||
just beginning your creative journey, we have everything you need
|
||||
to bring your ideas to life.
|
||||
</p>
|
||||
<p>
|
||||
Explore our collection of washi tapes, stickers, stamps, and more.
|
||||
Each item is carefully selected to help you create something
|
||||
beautiful and meaningful.
|
||||
</p>
|
||||
</div>
|
||||
<div class="inspiration-image" id="promotionImage">
|
||||
<img
|
||||
src="/assets/images/inspiration.jpg"
|
||||
alt="Creative Inspiration"
|
||||
loading="lazy"
|
||||
onerror="this.style.display='none'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/portfolio" class="btn btn-secondary">View Portfolio</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Featured Products / Portfolio Section -->
|
||||
<section class="collection" id="portfolioSection">
|
||||
<div class="container">
|
||||
<h2 id="portfolioTitle">Featured Products</h2>
|
||||
<p class="section-subtitle" id="portfolioDescription">
|
||||
Discover our most popular items
|
||||
</p>
|
||||
<div class="products-grid" id="featuredProducts">
|
||||
<div class="product-card">
|
||||
<div class="product-image">
|
||||
<img
|
||||
src="/assets/images/placeholder.svg"
|
||||
alt="Product"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
<h3>Loading products...</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 40px">
|
||||
<a href="/shop" class="btn btn-secondary">View All Products</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-grid">
|
||||
<div class="footer-col">
|
||||
<h3 class="footer-title" id="footerSiteName">Sky Art Shop</h3>
|
||||
<p class="footer-text">
|
||||
Your destination for unique art pieces and creative supplies.
|
||||
</p>
|
||||
<div class="social-links">
|
||||
<a href="#" class="social-link"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#" class="social-link"
|
||||
><i class="bi bi-instagram"></i
|
||||
></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-twitter"></i></a>
|
||||
<a href="#" class="social-link"
|
||||
><i class="bi bi-pinterest"></i
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Shop</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shop">All Products</a></li>
|
||||
<li><a href="/shop?category=paintings">Paintings</a></li>
|
||||
<li><a href="/shop?category=prints">Prints</a></li>
|
||||
<li><a href="/shop?category=supplies">Art Supplies</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">About</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/about">Our Story</a></li>
|
||||
<li><a href="/portfolio">Portfolio</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Customer Service</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/privacy">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p id="footerText">© 2025 Sky Art Shop. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="/assets/js/api-cache.js"></script>
|
||||
<script src="/assets/js/main.js"></script>
|
||||
<script src="/assets/js/shop-system.js"></script>
|
||||
<script src="/assets/js/page-transitions.js?v=1766709739"></script>
|
||||
<script src="/assets/js/back-button-control.js?v=1766723554"></script>
|
||||
<script>
|
||||
// Load homepage settings
|
||||
async function loadHomepageSettings() {
|
||||
try {
|
||||
const response = await fetch("/api/homepage/settings");
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.success && data.settings) {
|
||||
applyHomepageSettings(data.settings);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Using default homepage settings");
|
||||
}
|
||||
}
|
||||
|
||||
function applyHomepageSettings(settings) {
|
||||
// Apply Hero Section
|
||||
if (settings.hero) {
|
||||
const heroSection = document.getElementById("heroSection");
|
||||
const heroContent = document.getElementById("heroContent");
|
||||
|
||||
if (!settings.hero.enabled) {
|
||||
heroSection.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings.hero.headline) {
|
||||
document.getElementById("heroHeadline").textContent =
|
||||
settings.hero.headline;
|
||||
}
|
||||
|
||||
if (settings.hero.subheading) {
|
||||
document.getElementById("heroSubheading").textContent =
|
||||
settings.hero.subheading;
|
||||
}
|
||||
|
||||
if (settings.hero.description) {
|
||||
document.getElementById("heroDescription").innerHTML =
|
||||
settings.hero.description;
|
||||
}
|
||||
|
||||
if (settings.hero.ctaText && settings.hero.ctaLink) {
|
||||
const ctaBtn = document.getElementById("heroCtaBtn");
|
||||
ctaBtn.textContent = settings.hero.ctaText;
|
||||
ctaBtn.href = settings.hero.ctaLink;
|
||||
}
|
||||
|
||||
if (settings.hero.backgroundUrl) {
|
||||
const isVideo =
|
||||
settings.hero.backgroundUrl.match(/\.(mp4|webm|ogg)$/i);
|
||||
const heroImageContainer =
|
||||
document.getElementById("heroImageContainer");
|
||||
|
||||
if (isVideo) {
|
||||
heroImageContainer.innerHTML = `
|
||||
<video autoplay muted loop playsinline style="width: 100%; height: 100%; object-fit: cover;">
|
||||
<source src="${settings.hero.backgroundUrl}" type="video/mp4">
|
||||
</video>
|
||||
`;
|
||||
} else {
|
||||
heroImageContainer.innerHTML = `<img src="${settings.hero.backgroundUrl}" alt="Hero Background" loading="lazy" />`;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply layout
|
||||
if (settings.hero.layout) {
|
||||
heroContent.style.textAlign = settings.hero.layout.replace(
|
||||
"text-",
|
||||
""
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply Promotion Section
|
||||
if (settings.promotion) {
|
||||
const promotionSection = document.getElementById("promotionSection");
|
||||
|
||||
if (!settings.promotion.enabled) {
|
||||
promotionSection.style.display = "none";
|
||||
} else {
|
||||
if (settings.promotion.title) {
|
||||
document.getElementById("promotionTitle").textContent =
|
||||
settings.promotion.title;
|
||||
}
|
||||
|
||||
if (settings.promotion.description) {
|
||||
document.getElementById("promotionText").innerHTML =
|
||||
settings.promotion.description;
|
||||
}
|
||||
|
||||
if (settings.promotion.imageUrl) {
|
||||
const promotionImage = document.getElementById("promotionImage");
|
||||
promotionImage.innerHTML = `<img src="${
|
||||
settings.promotion.imageUrl
|
||||
}" alt="${
|
||||
settings.promotion.title || "Promotion"
|
||||
}" loading="lazy" />`;
|
||||
}
|
||||
|
||||
// Apply text alignment
|
||||
if (settings.promotion.textAlignment) {
|
||||
document.getElementById("promotionText").style.textAlign =
|
||||
settings.promotion.textAlignment;
|
||||
}
|
||||
|
||||
// Apply image position (you can customize CSS classes for this)
|
||||
const promotionContent =
|
||||
document.getElementById("promotionContent");
|
||||
if (settings.promotion.imagePosition === "right") {
|
||||
promotionContent.style.flexDirection = "row-reverse";
|
||||
} else if (settings.promotion.imagePosition === "center") {
|
||||
promotionContent.style.flexDirection = "column";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply Portfolio Section
|
||||
if (settings.portfolio) {
|
||||
const portfolioSection = document.getElementById("portfolioSection");
|
||||
|
||||
if (!settings.portfolio.enabled) {
|
||||
portfolioSection.style.display = "none";
|
||||
} else {
|
||||
if (settings.portfolio.title) {
|
||||
document.getElementById("portfolioTitle").textContent =
|
||||
settings.portfolio.title;
|
||||
}
|
||||
|
||||
if (settings.portfolio.description) {
|
||||
const descEl = document.getElementById("portfolioDescription");
|
||||
if (descEl) {
|
||||
descEl.innerHTML = settings.portfolio.description;
|
||||
}
|
||||
}
|
||||
|
||||
// Portfolio count is handled by existing featured products logic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load site settings
|
||||
async function loadSiteSettings() {
|
||||
try {
|
||||
const response = await fetch("/api/settings");
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.settings) {
|
||||
document.getElementById("siteName").textContent =
|
||||
data.settings.sitename || "Sky Art Shop";
|
||||
document.getElementById("footerSiteName").textContent =
|
||||
data.settings.sitename || "Sky Art Shop";
|
||||
document.getElementById("footerText").textContent =
|
||||
data.settings.footertext ||
|
||||
"© 2025 by Sky Art Shop. All rights reserved.";
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Using default site settings");
|
||||
}
|
||||
}
|
||||
|
||||
// Load featured products
|
||||
async function loadFeaturedProducts() {
|
||||
try {
|
||||
const response = await window.apiCache.fetch("/api/products/featured?limit=4");
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.products && data.products.length > 0) {
|
||||
const container = document.getElementById("featuredProducts");
|
||||
container.innerHTML = data.products
|
||||
.map((product) => {
|
||||
// Get product image (primary or first from images array)
|
||||
let productImage = "/assets/images/placeholder.svg";
|
||||
if (
|
||||
product.images &&
|
||||
Array.isArray(product.images) &&
|
||||
product.images.length > 0
|
||||
) {
|
||||
const primaryImg = product.images.find(
|
||||
(img) => img.is_primary
|
||||
);
|
||||
productImage = primaryImg
|
||||
? primaryImg.image_url
|
||||
: product.images[0].image_url;
|
||||
} else if (product.imageurl) {
|
||||
productImage = product.imageurl;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="product-card">
|
||||
<a href="/product?id=${
|
||||
product.id
|
||||
}" class="product-link">
|
||||
<div class="product-image">
|
||||
<img src="${productImage}" alt="${
|
||||
product.name
|
||||
}" loading="lazy" onerror="this.src='/assets/images/placeholder.svg'" />
|
||||
</div>
|
||||
</a>
|
||||
<div class="product-info">
|
||||
<a href="/product?id=${
|
||||
product.id
|
||||
}" class="product-title-link">
|
||||
<h3>${product.name}</h3>
|
||||
</a>
|
||||
${
|
||||
product.shortdescription ||
|
||||
product.description
|
||||
? `<div class="product-description">${
|
||||
product.shortdescription ||
|
||||
(product.description
|
||||
? product.description.substring(
|
||||
0,
|
||||
100
|
||||
) + "..."
|
||||
: "")
|
||||
}</div>`
|
||||
: ""
|
||||
}
|
||||
<p class="price">$${parseFloat(
|
||||
product.price
|
||||
).toFixed(2)}</p>
|
||||
</div>
|
||||
<div class="product-actions">
|
||||
<button class="btn btn-small btn-icon" onclick="addToWishlist('${
|
||||
product.id
|
||||
}', '${product.name}', ${
|
||||
product.price
|
||||
}, '${productImage}')" aria-label="Add to wishlist">
|
||||
<i class="bi bi-heart"></i>
|
||||
</button>
|
||||
<button class="btn btn-small btn-icon" onclick="addToCart('${
|
||||
product.id
|
||||
}', '${product.name}', ${
|
||||
product.price
|
||||
}, '${productImage}')" aria-label="Add to cart">
|
||||
<i class="bi bi-cart-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Could not load featured products");
|
||||
}
|
||||
}
|
||||
|
||||
// Cart and Wishlist Functions
|
||||
function addToCart(productId, name, price, imageurl) {
|
||||
window.ShopSystem.addToCart(
|
||||
{
|
||||
id: String(productId),
|
||||
name,
|
||||
price: parseFloat(price),
|
||||
imageurl,
|
||||
},
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
function addToWishlist(productId, name, price, imageurl) {
|
||||
window.ShopSystem.addToWishlist({
|
||||
id: String(productId),
|
||||
name,
|
||||
price: parseFloat(price),
|
||||
imageurl,
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize
|
||||
loadSiteSettings();
|
||||
loadHomepageSettings();
|
||||
loadFeaturedProducts();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
482
website/public/old-backup/portfolio.html
Normal file
482
website/public/old-backup/portfolio.html
Normal file
@@ -0,0 +1,482 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- Security Headers -->
|
||||
<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 rel="stylesheet" href="/assets/css/theme-colors.css" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/page-overrides.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/cart-wishlist.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
<link rel="stylesheet" href="/assets/css/responsive.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script>window.__bodyReady=true</script>
|
||||
<div class="sticky-banner-wrapper">
|
||||
<!-- Modern Navigation -->
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home" class="brand-link">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
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" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio" class="nav-link active">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog" class="nav-link">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact" 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" 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"><i class="bi bi-cart-x"></i><br>Your cart is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<a href="/shop" class="btn-outline">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" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact" class="mobile-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mobile-menu-overlay" id="mobileMenuOverlay"></div>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
// Mobile Menu Toggle
|
||||
(function() {
|
||||
const mobileToggle = document.getElementById('mobileMenuToggle');
|
||||
const mobileMenu = document.getElementById('mobileMenu');
|
||||
const mobileClose = document.getElementById('mobileMenuClose');
|
||||
const overlay = document.getElementById('mobileMenuOverlay');
|
||||
|
||||
function openMenu() {
|
||||
mobileMenu.classList.add('active');
|
||||
overlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
mobileMenu.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
if (mobileToggle) mobileToggle.addEventListener('click', openMenu);
|
||||
if (mobileClose) mobileClose.addEventListener('click', closeMenu);
|
||||
if (overlay) overlay.addEventListener('click', closeMenu);
|
||||
|
||||
// Close on ESC key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && mobileMenu.classList.contains('active')) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<section class="about-hero">
|
||||
<div class="container">
|
||||
<h1>Portfolio</h1>
|
||||
<p class="hero-subtitle">Explore our creative projects and artwork</p>
|
||||
</section>
|
||||
<section
|
||||
class="portfolio-section"
|
||||
style="padding: 60px 0; background: #ffebeb"
|
||||
>
|
||||
<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-grid">
|
||||
<div class="footer-col">
|
||||
<h3 class="footer-title">Sky Art Shop</h3>
|
||||
<p class="footer-text">
|
||||
Your destination for unique art pieces and creative supplies.
|
||||
</p>
|
||||
<div class="social-links">
|
||||
<a href="#" class="social-link"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-instagram"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-twitter"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-pinterest"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Shop</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shop">All Products</a></li>
|
||||
<li><a href="/shop?category=paintings">Paintings</a></li>
|
||||
<li><a href="/shop?category=prints">Prints</a></li>
|
||||
<li><a href="/shop?category=supplies">Art Supplies</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">About</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/about">Our Story</a></li>
|
||||
<li><a href="/portfolio">Portfolio</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Customer Service</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/privacy">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 Sky Art Shop. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Project Modal -->
|
||||
<div
|
||||
id="projectModal"
|
||||
style="
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
z-index: 9999;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 40px 0;
|
||||
"
|
||||
onclick="if(event.target.id === 'projectModal') closeProjectModal();"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
position: relative;
|
||||
margin: auto;
|
||||
width: 90%;
|
||||
max-width: 900px;
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
||||
"
|
||||
onclick="event.stopPropagation();"
|
||||
>
|
||||
<button
|
||||
onclick="closeProjectModal()"
|
||||
style="
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: white;
|
||||
border: none;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.2s;
|
||||
"
|
||||
onmouseover="this.style.transform='scale(1.1)'; this.style.background='#f8f9fa';"
|
||||
onmouseout="this.style.transform='scale(1)'; this.style.background='white';"
|
||||
>
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
<div
|
||||
id="modalContent"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Core dependencies (load in order) -->
|
||||
<script src="/assets/js/api-cache.js"></script>
|
||||
<script src="/assets/js/main.js"></script>
|
||||
<script src="/assets/js/shop-system.js"></script>
|
||||
|
||||
<!-- UI enhancements -->
|
||||
<script src="/assets/js/page-transitions.js?v=1766709739"></script>
|
||||
<script src="/assets/js/back-button-control.js?v=1766723554"></script>
|
||||
<script src="/assets/js/navigation.js"></script>
|
||||
|
||||
<!-- Page-specific functionality -->
|
||||
<script>
|
||||
let portfolioProjects = [];
|
||||
|
||||
// Open project modal
|
||||
function openProjectModal(projectId) {
|
||||
const project = portfolioProjects.find((p) => p.id === projectId);
|
||||
if (!project) {
|
||||
console.error('[Portfolio] Project not found:', projectId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!project.title) {
|
||||
console.error('[Portfolio] Invalid project data - missing title:', project);
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = document.getElementById("projectModal");
|
||||
const modalContent = document.getElementById("modalContent");
|
||||
|
||||
if (!modal || !modalContent) {
|
||||
console.error('[Portfolio] Modal elements not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Build category badge HTML
|
||||
const categoryBadge = project.category
|
||||
? `<span style="display: inline-block; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 8px 18px; border-radius: 24px; font-size: 13px; font-weight: 600; margin-bottom: 24px; letter-spacing: 0.5px; text-transform: uppercase;">${project.category}</span>`
|
||||
: '';
|
||||
|
||||
// Build modal content
|
||||
modalContent.innerHTML = `
|
||||
<div class="project-image" style="width: 100%; height: 450px; overflow: hidden; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); flex-shrink: 0;">
|
||||
<img src="${project.featuredimage || project.imageurl || '/assets/images/placeholder.svg'}"
|
||||
alt="${project.title}"
|
||||
onerror="this.src='/assets/images/placeholder.svg'"
|
||||
style="width: 100%; height: 100%; object-fit: cover;" />
|
||||
</div>
|
||||
<div style="padding: 40px; background: white;">
|
||||
${categoryBadge}
|
||||
<h2 style="font-size: 36px; font-weight: 700; margin: 0 0 24px 0; color: #1a1a1a; line-height: 1.2;">${project.title}</h2>
|
||||
<div style="color: #555; font-size: 17px; line-height: 1.9; margin-bottom: 32px; font-weight: 400;">
|
||||
${project.description || 'No description available.'}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.style.display = "block";
|
||||
document.body.style.overflow = "hidden";
|
||||
document.documentElement.style.overflow = "hidden";
|
||||
}
|
||||
// Close project modal
|
||||
function closeProjectModal() {
|
||||
document.getElementById("projectModal").style.display = "none";
|
||||
document.body.style.overflow = "auto";
|
||||
document.documentElement.style.overflow = "auto";
|
||||
}
|
||||
|
||||
// Close modal on outside click
|
||||
document.addEventListener("click", (e) => {
|
||||
const modal = document.getElementById("projectModal");
|
||||
if (e.target === modal) {
|
||||
closeProjectModal();
|
||||
}
|
||||
});
|
||||
// Close modal on Escape key
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") {
|
||||
closeProjectModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Load portfolio projects from API
|
||||
async function loadPortfolio() {
|
||||
try {
|
||||
const response = await window.apiCache.fetch("/api/portfolio/projects");
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
portfolioProjects = data.projects || [];
|
||||
document.getElementById("loadingMessage").style.display = "none";
|
||||
if (portfolioProjects.length === 0) {
|
||||
document.getElementById("noProjects").style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate and filter projects
|
||||
const validProjects = portfolioProjects.filter(project => {
|
||||
if (!project || !project.id || !project.title) {
|
||||
console.warn('[Portfolio] Skipping invalid project:', project);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (validProjects.length === 0) {
|
||||
document.getElementById("noProjects").style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
const grid = document.getElementById("portfolioGrid");
|
||||
grid.innerHTML = validProjects
|
||||
.map(
|
||||
(project) => `
|
||||
<div class="product-card" onclick="openProjectModal('${
|
||||
project.id
|
||||
}')" style="background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); transition: all 0.3s; cursor: pointer;">
|
||||
<div class="product-image" style="position: relative; padding-top: 100%; overflow: hidden; background: #f5f5f5;">
|
||||
<img src="${
|
||||
project.featuredimage || project.imageurl || "/assets/images/placeholder.svg"
|
||||
}"
|
||||
alt="${project.title}"
|
||||
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; transition: transform 0.3s;"
|
||||
loading="lazy"
|
||||
onerror="this.src='/assets/images/placeholder.svg'"
|
||||
onmouseover="this.style.transform='scale(1.05)'"
|
||||
onmouseout="this.style.transform='scale(1)'" />
|
||||
${
|
||||
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; text-align: center;">
|
||||
<h3 style="font-size: 18px; font-weight: 600; margin: 0; color: #333;">${
|
||||
project.title
|
||||
}</h3>
|
||||
${
|
||||
project.description && project.description.replace(/<[^>]*>/g, '').trim()
|
||||
? `<p style="font-size: 14px; color: #666; margin: 8px 0 0 0; line-height: 1.5;">${project.description.replace(/<[^>]*>/g, '').substring(0, 80)}${project.description.length > 80 ? '...' : ''}</p>`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
} else {
|
||||
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>
|
||||
853
website/public/old-backup/product.html
Normal file
853
website/public/old-backup/product.html
Normal file
@@ -0,0 +1,853 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- Security Headers -->
|
||||
<title>Product Details - 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?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/cart-wishlist.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
<link rel="stylesheet" href="/assets/css/responsive.css" />
|
||||
<link rel="stylesheet" href="/assets/css/theme-colors.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- Modern Navigation -->
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home" class="brand-link">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
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" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio" class="nav-link">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog" class="nav-link">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact" 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" 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">
|
||||
<a href="/shop" class="btn-outline">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" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact" class="mobile-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div
|
||||
id="loading"
|
||||
style="
|
||||
text-align: center;
|
||||
padding: 100px 20px;
|
||||
font-size: 18px;
|
||||
color: #202023;
|
||||
background: #ffebeb;
|
||||
"
|
||||
>
|
||||
<i
|
||||
class="bi bi-hourglass-split"
|
||||
style="font-size: 48px; display: block; margin-bottom: 20px"
|
||||
></i>
|
||||
Loading product...
|
||||
</div>
|
||||
|
||||
<div id="productDetail" style="display: none"></div>
|
||||
|
||||
<!-- Core Scripts (load in dependency order) -->
|
||||
<script src="/assets/js/api-cache.js"></script>
|
||||
<script src="/assets/js/main.js"></script>
|
||||
<script src="/assets/js/shop-system.js"></script>
|
||||
<script src="/assets/js/api-client.js"></script>
|
||||
<script src="/assets/js/notifications.js"></script>
|
||||
<script src="/assets/js/page-transitions.js?v=1766709739"></script>
|
||||
<script src="/assets/js/back-button-control.js?v=1766723554"></script>
|
||||
<script src="/assets/js/navigation.js?v=1766708114"></script>
|
||||
<script>
|
||||
// Function to change primary image
|
||||
function changePrimaryImage(imageUrl, index) {
|
||||
const primaryImg = document.getElementById("primaryImage");
|
||||
if (primaryImg) {
|
||||
primaryImg.src = imageUrl;
|
||||
}
|
||||
|
||||
// Update thumbnail borders
|
||||
const thumbnails = document.querySelectorAll(".thumbnail-item");
|
||||
thumbnails.forEach((thumb, idx) => {
|
||||
if (idx === index) {
|
||||
thumb.style.border = "2px solid #6b46c1";
|
||||
} else {
|
||||
thumb.style.border = "1px solid #e5e7eb";
|
||||
}
|
||||
});
|
||||
|
||||
// Update color variant borders
|
||||
const variants = document.querySelectorAll(".color-variant-circle");
|
||||
variants.forEach((variant) => {
|
||||
const onclick = variant.getAttribute("onclick");
|
||||
if (onclick && onclick.includes(imageUrl)) {
|
||||
variant.style.border = "3px solid #6b46c1";
|
||||
} else {
|
||||
variant.style.border = "3px solid #e5e7eb";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function loadProduct() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const productId = params.get("id");
|
||||
|
||||
console.log("Product page loaded. URL:", window.location.href);
|
||||
console.log("Product ID from URL:", productId);
|
||||
|
||||
if (!productId) {
|
||||
console.error("No product ID in URL");
|
||||
document.getElementById("loading").innerHTML =
|
||||
'<p style="text-align: center; padding: 40px; color: #ef4444; font-size: 18px;">Product not found - No product ID in URL</p><div style="text-align: center;"><a href="/shop" style="color: #FCB1D8; text-decoration: none; font-weight: 600;">← Back to Shop</a></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(
|
||||
"Fetching product from API:",
|
||||
`/api/products/${productId}`
|
||||
);
|
||||
const response = await window.apiCache.fetch(
|
||||
`/api/products/${productId}`
|
||||
);
|
||||
const data = await response.json();
|
||||
console.log("API response:", data);
|
||||
|
||||
if (!data.success || !data.product) {
|
||||
throw new Error("Product not found");
|
||||
}
|
||||
|
||||
const product = data.product;
|
||||
document.title = `${product.name} - Sky Art Shop`;
|
||||
|
||||
// Get primary image or first image from images array
|
||||
let primaryImage = "/assets/images/placeholder.svg";
|
||||
let imageGallery = [];
|
||||
|
||||
if (
|
||||
product.images &&
|
||||
Array.isArray(product.images) &&
|
||||
product.images.length > 0
|
||||
) {
|
||||
// Find primary image
|
||||
const primary = product.images.find((img) => img.is_primary);
|
||||
if (primary) {
|
||||
primaryImage = primary.image_url;
|
||||
} else {
|
||||
primaryImage = product.images[0].image_url;
|
||||
}
|
||||
imageGallery = product.images;
|
||||
}
|
||||
|
||||
// Build image gallery HTML
|
||||
let galleryHTML = "";
|
||||
if (imageGallery.length > 0) {
|
||||
galleryHTML = `
|
||||
<div style="display: flex; gap: 12px; margin-top: 16px; overflow-x: auto; padding: 8px 0;">
|
||||
${imageGallery
|
||||
.map(
|
||||
(img, idx) => `
|
||||
<img src="${img.image_url}"
|
||||
alt="${img.alt_text || product.name}"
|
||||
onclick="changePrimaryImage('${img.image_url}')"
|
||||
style="width: 80px; height: 80px; object-fit: cover; border-radius: 8px; cursor: pointer; border: ${
|
||||
img.image_url === primaryImage
|
||||
? "3px solid #6b46c1"
|
||||
: "1px solid #e5e7eb"
|
||||
};"
|
||||
onerror="this.src='/assets/images/placeholder.svg'" />
|
||||
`
|
||||
)
|
||||
.join("")}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Build product details HTML
|
||||
let detailsHTML = "";
|
||||
if (
|
||||
product.sku ||
|
||||
product.weight ||
|
||||
product.dimensions ||
|
||||
product.material
|
||||
) {
|
||||
detailsHTML = `
|
||||
<div style="margin-bottom: 24px; padding: 20px; background: #FFD0D0; border-radius: 8px; border: 1px solid #FCB1D8;">
|
||||
<h3 style="font-size: 16px; font-weight: 600; color: #202023; margin-bottom: 16px;">Product Details</h3>
|
||||
${
|
||||
product.sku
|
||||
? `
|
||||
<p style="margin-bottom: 8px; color: #202023; opacity: 0.8;">
|
||||
<span style="font-weight: 500;">SKU:</span> ${product.sku}
|
||||
</p>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
product.weight
|
||||
? `
|
||||
<p style="margin-bottom: 8px; color: #202023; opacity: 0.8;">
|
||||
<span style="font-weight: 500;">Weight:</span> ${product.weight}
|
||||
</p>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
product.dimensions
|
||||
? `
|
||||
<p style="margin-bottom: 8px; color: #202023; opacity: 0.8;">
|
||||
<span style="font-weight: 500;">Dimensions:</span> ${product.dimensions}
|
||||
</p>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
product.material
|
||||
? `
|
||||
<p style="margin-bottom: 8px; color: #6b7280;">
|
||||
<span style="font-weight: 500;">Material:</span> ${product.material}
|
||||
</p>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Build badges HTML
|
||||
let badgesHTML = "";
|
||||
if (product.isfeatured || product.isbestseller) {
|
||||
badgesHTML = `
|
||||
<div style="display: flex; gap: 8px; margin-bottom: 16px;">
|
||||
${
|
||||
product.isfeatured
|
||||
? `
|
||||
<span style="display: inline-block; padding: 6px 12px; background: #FCB1D8; color: #202023; border-radius: 6px; font-size: 12px; font-weight: 600; box-shadow: 0 2px 4px rgba(252, 177, 216, 0.4);">
|
||||
<i class="bi bi-star-fill"></i> Featured
|
||||
</span>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
product.isbestseller
|
||||
? `
|
||||
<span style="display: inline-block; padding: 6px 12px; background: #F6CCDE; color: #202023; border-radius: 6px; font-size: 12px; font-weight: 600; box-shadow: 0 2px 4px rgba(252, 177, 216, 0.4);">
|
||||
<i class="bi bi-trophy-fill"></i> Best Seller
|
||||
</span>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
document.getElementById("productDetail").innerHTML = `
|
||||
<div style="font-family: 'Roboto', sans-serif;">
|
||||
<nav style="background: #F6CCDE; padding: 16px 24px; box-shadow: 0 1px 3px rgba(252, 177, 216, 0.3);">
|
||||
<div style="max-width: 1400px; margin: 0 auto; display: flex; align-items: center; gap: 20px;">
|
||||
<a href="/home" style="font-size: 20px; font-weight: 600; color: #202023; text-decoration: none;">Sky Art Shop</a>
|
||||
<span style="color: #202023; opacity: 0.5;">/</span>
|
||||
<a href="/shop" style="color: #202023; opacity: 0.8; text-decoration: none; transition: opacity 0.3s;" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='0.8'">Shop</a>
|
||||
<span style="color: #202023; opacity: 0.5;">/</span>
|
||||
<span style="color: #202023; opacity: 0.7;">${
|
||||
product.name
|
||||
}</span>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div style="max-width: 1400px; margin: 40px auto; padding: 0 24px; background: #FFEBEB; border-radius: 12px; padding: 40px 24px;">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 60px;">
|
||||
|
||||
<!-- LEFT COLUMN: Image & Description -->
|
||||
<div>
|
||||
<!-- Back to Shop Link -->
|
||||
<a href="/shop" style="display: inline-flex; align-items: center; gap: 8px; margin-bottom: 20px; color: #FCB1D8; text-decoration: none; font-weight: 600; font-size: 15px; transition: all 0.3s;"
|
||||
onmouseover="this.style.gap='12px'; this.style.color='#d896c0'"
|
||||
onmouseout="this.style.gap='8px'; this.style.color='#FCB1D8'">
|
||||
<i class="bi bi-arrow-left"></i> Back to Shop
|
||||
</a>
|
||||
|
||||
<!-- Image Section with Thumbnails -->
|
||||
<div style="display: flex; gap: 16px;">
|
||||
<!-- Thumbnail Gallery (Vertical Left) -->
|
||||
${
|
||||
imageGallery.length > 1
|
||||
? `
|
||||
<div id="thumbnailGallery" style="display: flex; flex-direction: column; gap: 12px; max-height: 600px; overflow-y: auto;">
|
||||
${imageGallery
|
||||
.map(
|
||||
(img, idx) => `
|
||||
<div onclick="changePrimaryImage('${
|
||||
img.image_url
|
||||
}', ${idx})"
|
||||
class="thumbnail-item"
|
||||
data-index="${idx}"
|
||||
style="width: 70px; height: 70px; border-radius: 4px; overflow: hidden; cursor: pointer; border: ${
|
||||
img.image_url === primaryImage
|
||||
? "2px solid #6b46c1"
|
||||
: "1px solid #e5e7eb"
|
||||
}; transition: all 0.3s; flex-shrink: 0;">
|
||||
<img src="${img.image_url}"
|
||||
alt="${img.alt_text || product.name}"
|
||||
style="width: 100%; height: 100%; object-fit: cover;"
|
||||
onerror="this.src='/assets/images/placeholder.svg'" />
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("")}
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
<!-- Main Product Image -->
|
||||
<div style="flex: 1; background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(252, 177, 216, 0.2); border: 1px solid #FFD0D0; padding: 20px; display: flex; align-items: center; justify-content: center; min-height: 500px; max-height: 600px;">
|
||||
<img id="primaryImage"
|
||||
src="${primaryImage}"
|
||||
alt="${product.name}"
|
||||
style="max-width: 100%; max-height: 560px; width: auto; height: auto; object-fit: contain; display: block;"
|
||||
onerror="this.src='/assets/images/placeholder.svg'" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Color Variants Section -->
|
||||
${
|
||||
imageGallery.some(
|
||||
(img) => img.color_variant && img.color_code
|
||||
)
|
||||
? `
|
||||
<div style="margin-top: 24px; padding: 20px; background: #FFD0D0; border-radius: 12px;">
|
||||
<h4 style="font-size: 14px; font-weight: 600; color: #1a1a1a; margin-bottom: 12px; display: flex; align-items: center; gap: 8px;">
|
||||
<i class="bi bi-palette"></i>
|
||||
Available Colors (${
|
||||
imageGallery.filter((img) => img.color_variant).length
|
||||
})
|
||||
</h4>
|
||||
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
${imageGallery
|
||||
.filter((img) => img.color_variant && img.color_code)
|
||||
.map(
|
||||
(img, idx) => `
|
||||
<div onclick="changePrimaryImage('${
|
||||
img.image_url
|
||||
}', ${imageGallery.indexOf(img)})"
|
||||
class="color-variant-circle"
|
||||
title="${img.color_variant}"
|
||||
style="position: relative; width: 48px; height: 48px; border-radius: 50%; cursor: pointer; border: 3px solid ${
|
||||
img.image_url === primaryImage
|
||||
? "#FCB1D8"
|
||||
: "#FFEBEB"
|
||||
}; transition: all 0.3s; overflow: hidden; box-shadow: 0 2px 4px rgba(252, 177, 216, 0.3);"
|
||||
onmouseover="this.querySelector('.color-name-tooltip').style.opacity='1'; this.querySelector('.color-name-tooltip').style.transform='translateY(0)'; this.style.transform='scale(1.15)'"
|
||||
onmouseout="this.querySelector('.color-name-tooltip').style.opacity='0'; this.querySelector('.color-name-tooltip').style.transform='translateY(-5px)'; this.style.transform='scale(1)'">
|
||||
<div style="width: 100%; height: 100%; background: ${
|
||||
img.color_code
|
||||
};"></div>
|
||||
<div class="color-name-tooltip" style="position: absolute; bottom: -35px; left: 50%; transform: translateX(-50%) translateY(-5px); background: rgba(0,0,0,0.9); color: white; padding: 6px 12px; border-radius: 6px; font-size: 12px; white-space: nowrap; pointer-events: none; opacity: 0; transition: all 0.3s; z-index: 10;">
|
||||
${img.color_variant}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("")}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
<!-- Description Box -->
|
||||
${
|
||||
product.description
|
||||
? `
|
||||
<div style="margin-top: 24px; padding: 20px; background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(252, 177, 216, 0.2); border: 1px solid #FFD0D0;">
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; padding-bottom: 12px; border-bottom: 2px solid #FCB1D8;">
|
||||
<i class="bi bi-pin-angle-fill" style="color: #FCB1D8; font-size: 18px;"></i>
|
||||
<h3 style="font-size: 16px; font-weight: 600; color: #202023; margin: 0;">Description</h3>
|
||||
</div>
|
||||
<div style="max-height: 200px; overflow-y: auto; color: #4b5563; line-height: 1.7; font-size: 15px; padding-right: 8px;">
|
||||
${product.description}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- RIGHT COLUMN: Product Info & Actions -->
|
||||
<div style="padding: 20px 0;">
|
||||
${badgesHTML}
|
||||
<h1 style="font-size: 36px; font-weight: 700; color: #1a1a1a; margin: 0 0 16px 0; line-height: 1.2;">${
|
||||
product.name
|
||||
}</h1>
|
||||
|
||||
<div style="display: flex; align-items: baseline; gap: 16px; margin-bottom: 24px;">
|
||||
<p style="font-size: 36px; font-weight: 700; color: #FCB1D8; margin: 0;">$${parseFloat(
|
||||
product.price
|
||||
).toFixed(2)}</p>
|
||||
${
|
||||
product.stockquantity > 0
|
||||
? `<span style="color: #10b981; font-weight: 500; display: flex; align-items: center; gap: 6px;"><i class="bi bi-check-circle-fill"></i> In Stock (${product.stockquantity} available)</span>`
|
||||
: `<span style="color: #ef4444; font-weight: 500; display: flex; align-items: center; gap: 6px;"><i class="bi bi-x-circle-fill"></i> Out of Stock</span>`
|
||||
}
|
||||
</div>
|
||||
|
||||
${
|
||||
product.shortdescription
|
||||
? `
|
||||
<p style="font-size: 18px; color: #4b5563; line-height: 1.6; margin-bottom: 24px;">${product.shortdescription}</p>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
${
|
||||
product.category
|
||||
? `
|
||||
<p style="margin-bottom: 24px;">
|
||||
<span style="font-weight: 500; color: #6b7280;">Category:</span>
|
||||
<span style="display: inline-block; margin-left: 8px; padding: 6px 14px; background: #FFD0D0; border-radius: 6px; font-size: 14px; color: #202023;">
|
||||
<i class="bi bi-tag"></i> ${product.category}
|
||||
</span>
|
||||
</p>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
<!-- Product Details (Moved from description area) -->
|
||||
${detailsHTML}
|
||||
|
||||
<!-- Add to Cart & Wishlist Buttons -->
|
||||
<div style="display: flex; gap: 12px; margin-top: 32px; margin-bottom: 24px;">
|
||||
<button onclick="addToCart()"
|
||||
${product.stockquantity <= 0 ? "disabled" : ""}
|
||||
style="padding: 14px 24px; background: ${
|
||||
product.stockquantity <= 0 ? "#9ca3af" : "#FCB1D8"
|
||||
}; color: #202023; border: none; border-radius: 10px; font-size: 15px; font-weight: 600; cursor: ${
|
||||
product.stockquantity <= 0 ? "not-allowed" : "pointer"
|
||||
}; transition: all 0.3s; display: flex; align-items: center; justify-content: center; gap: 8px; box-shadow: 0 4px 12px rgba(252, 177, 216, 0.3);"
|
||||
onmouseover="if(${
|
||||
product.stockquantity > 0
|
||||
}) this.style.transform='translateY(-2px)'; this.style.background='#F6CCDE'; this.style.boxShadow='0 6px 16px rgba(252, 177, 216, 0.4)'"
|
||||
onmouseout="if(${
|
||||
product.stockquantity > 0
|
||||
}) this.style.transform='translateY(0)'; this.style.background='#FCB1D8'; this.style.boxShadow='0 4px 12px rgba(252, 177, 216, 0.3)'">
|
||||
<i class="bi bi-cart-plus" style="font-size: 20px;"></i>
|
||||
${
|
||||
product.stockquantity <= 0
|
||||
? "Out of Stock"
|
||||
: "Add to Cart"
|
||||
}
|
||||
</button>
|
||||
<button onclick="addToWishlist()"
|
||||
style="padding: 14px 20px; background: white; color: #FCB1D8; border: 2px solid #FCB1D8; border-radius: 10px; font-size: 18px; cursor: pointer; transition: all 0.3s;"
|
||||
onmouseover="this.style.background='#FCB1D8'; this.style.color='white'; this.style.transform='scale(1.05)'"
|
||||
onmouseout="this.style.background='white'; this.style.color='#FCB1D8'; this.style.transform='scale(1)'">
|
||||
<i class="bi bi-heart"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Related Products Section -->
|
||||
<div id="relatedProducts" style="margin-top: 60px; padding-top: 40px; border-top: 2px solid #e5e7eb;">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 32px;">
|
||||
<h2 style="font-size: 28px; font-weight: 700; color: #1a1a1a; margin: 0; display: flex; align-items: center; gap: 12px;">
|
||||
<i class="bi bi-box-seam" style="color: #6b46c1;"></i>
|
||||
You May Also Like
|
||||
</h2>
|
||||
</div>
|
||||
<div id="relatedProductsGrid" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 24px;">
|
||||
<!-- Products will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById("loading").style.display = "none";
|
||||
document.getElementById("productDetail").style.display = "block";
|
||||
|
||||
// Store product data with imageurl for shopping cart
|
||||
window.currentProduct = {
|
||||
...product,
|
||||
imageurl: primaryImage, // Add the primary image URL for cart display
|
||||
};
|
||||
|
||||
// Track viewed product for smart recommendations
|
||||
trackViewedProduct({
|
||||
...product,
|
||||
imageurl: primaryImage,
|
||||
});
|
||||
|
||||
// Load related products
|
||||
loadRelatedProducts(product.category, product.id);
|
||||
|
||||
console.log("Product loaded successfully:", product.name);
|
||||
} catch (error) {
|
||||
console.error("Error loading product:", error);
|
||||
document.getElementById("loading").innerHTML =
|
||||
'<div style="text-align: center; padding: 40px;"><p style="color: #ef4444; font-size: 18px; margin-bottom: 16px;">Error loading product</p><p style="color: #6b7280; margin-bottom: 20px;">' +
|
||||
error.message +
|
||||
'</p><a href="/shop" style="color: #FCB1D8; text-decoration: none; font-weight: 600;">← Back to Shop</a></div>';
|
||||
}
|
||||
}
|
||||
|
||||
function addToCart() {
|
||||
if (!window.currentProduct) {
|
||||
alert("Product not loaded. Please refresh the page.");
|
||||
return;
|
||||
}
|
||||
|
||||
window.ShopSystem.addToCart(window.currentProduct, 1);
|
||||
}
|
||||
|
||||
function addToWishlist() {
|
||||
if (!window.currentProduct) {
|
||||
alert("Product not loaded. Please refresh the page.");
|
||||
return;
|
||||
}
|
||||
|
||||
window.ShopSystem.addToWishlist(window.currentProduct);
|
||||
}
|
||||
|
||||
// Track viewed products for smart recommendations
|
||||
function trackViewedProduct(product) {
|
||||
try {
|
||||
let viewedProducts = JSON.parse(
|
||||
localStorage.getItem("skyart_viewed_products") || "[]"
|
||||
);
|
||||
|
||||
// Remove if already exists (to update timestamp)
|
||||
viewedProducts = viewedProducts.filter((p) => p.id !== product.id);
|
||||
|
||||
// Add to beginning of array
|
||||
viewedProducts.unshift({
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
category: product.category,
|
||||
imageurl: product.imageurl,
|
||||
price: product.price,
|
||||
viewedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Keep only last 20 viewed products
|
||||
viewedProducts = viewedProducts.slice(0, 20);
|
||||
|
||||
localStorage.setItem(
|
||||
"skyart_viewed_products",
|
||||
JSON.stringify(viewedProducts)
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Error tracking viewed product:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Load related products (same category + recently viewed)
|
||||
async function loadRelatedProducts(category, currentProductId) {
|
||||
try {
|
||||
const container = document.getElementById("relatedProductsGrid");
|
||||
if (!container) return;
|
||||
|
||||
// Show loading
|
||||
container.innerHTML =
|
||||
'<div style="grid-column: 1/-1; text-align: center; padding: 40px; color: #6b7280;">Loading recommendations...</div>';
|
||||
|
||||
// Fetch products from same category
|
||||
const response = await window.apiCache.fetch("/api/products");
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.products) {
|
||||
let relatedProducts = [];
|
||||
|
||||
// Get products from same category (excluding current)
|
||||
const sameCategoryProducts = data.products.filter(
|
||||
(p) => p.category === category && p.id !== currentProductId
|
||||
);
|
||||
|
||||
// Get recently viewed products
|
||||
const viewedProducts = JSON.parse(
|
||||
localStorage.getItem("skyart_viewed_products") || "[]"
|
||||
);
|
||||
const viewedIds = viewedProducts
|
||||
.map((p) => p.id)
|
||||
.filter((id) => id !== currentProductId);
|
||||
const recentlyViewedProducts = data.products.filter(
|
||||
(p) => viewedIds.includes(p.id) && p.id !== currentProductId
|
||||
);
|
||||
|
||||
// Combine: prioritize same category, then recently viewed
|
||||
relatedProducts = [...sameCategoryProducts];
|
||||
recentlyViewedProducts.forEach((p) => {
|
||||
if (!relatedProducts.find((rp) => rp.id === p.id)) {
|
||||
relatedProducts.push(p);
|
||||
}
|
||||
});
|
||||
|
||||
// Shuffle and limit to 4-8 products
|
||||
relatedProducts = shuffleArray(relatedProducts).slice(0, 8);
|
||||
|
||||
if (relatedProducts.length === 0) {
|
||||
container.innerHTML =
|
||||
'<div style="grid-column: 1/-1; text-align: center; padding: 40px; color: #6b7280;">No related products found.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Render products
|
||||
container.innerHTML = relatedProducts
|
||||
.map((product) => {
|
||||
// Get product image (primary or first from images array)
|
||||
let productImage = "/assets/images/placeholder.svg";
|
||||
if (
|
||||
product.images &&
|
||||
Array.isArray(product.images) &&
|
||||
product.images.length > 0
|
||||
) {
|
||||
const primaryImg = product.images.find(
|
||||
(img) => img.is_primary
|
||||
);
|
||||
productImage = primaryImg
|
||||
? primaryImg.image_url
|
||||
: product.images[0].image_url;
|
||||
} else if (product.imageurl) {
|
||||
productImage = product.imageurl;
|
||||
}
|
||||
|
||||
return `
|
||||
<a href="/product?id=${
|
||||
product.id
|
||||
}" style="text-decoration: none; color: inherit; display: block; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.08); transition: all 0.3s; border: 1px solid #e5e7eb;"
|
||||
onmouseover="this.style.transform='translateY(-4px)'; this.style.boxShadow='0 8px 16px rgba(0,0,0,0.12)'"
|
||||
onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 2px 8px rgba(0,0,0,0.08)'">
|
||||
<div style="aspect-ratio: 1; overflow: hidden; background: #f9fafb;">
|
||||
<img src="${productImage}"
|
||||
alt="${product.name}"
|
||||
style="width: 100%; height: 100%; object-fit: cover;"
|
||||
onerror="this.src='/assets/images/placeholder.svg'" />
|
||||
</div>
|
||||
<div style="padding: 16px;">
|
||||
<h3 style="font-size: 16px; font-weight: 600; color: #1a1a1a; margin: 0 0 8px 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
${product.name}
|
||||
</h3>
|
||||
${
|
||||
product.shortdescription || product.description
|
||||
? `
|
||||
<p style="font-size: 14px; color: #636e72; margin: 0 0 12px 0; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;">
|
||||
${
|
||||
product.shortdescription ||
|
||||
(product.description
|
||||
? product.description.substring(0, 80) + "..."
|
||||
: "")
|
||||
}
|
||||
</p>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
product.category
|
||||
? `
|
||||
<p style="font-size: 13px; color: #6b7280; margin: 0 0 12px 0;">
|
||||
<i class="bi bi-tag"></i> ${product.category}
|
||||
</p>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||||
<p style="font-size: 20px; font-weight: 700; color: #6b46c1; margin: 0;">
|
||||
$${parseFloat(product.price).toFixed(2)}
|
||||
</p>
|
||||
${
|
||||
product.stockquantity > 0
|
||||
? '<span style="font-size: 12px; color: #10b981; font-weight: 500;"><i class="bi bi-check-circle-fill"></i> In Stock</span>'
|
||||
: '<span style="font-size: 12px; color: #ef4444; font-weight: 500;"><i class="bi bi-x-circle-fill"></i> Out of Stock</span>'
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading related products:", error);
|
||||
const container = document.getElementById("relatedProductsGrid");
|
||||
if (container) {
|
||||
container.innerHTML =
|
||||
'<div style="grid-column: 1/-1; text-align: center; padding: 40px; color: #ef4444;">Error loading recommendations.</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shuffle array utility
|
||||
function shuffleArray(array) {
|
||||
const arr = [...array];
|
||||
for (let i = arr.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[arr[i], arr[j]] = [arr[j], arr[i]];
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
loadProduct();
|
||||
</script>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-grid">
|
||||
<div class="footer-col">
|
||||
<h3 class="footer-title" id="footerSiteName">Sky Art Shop</h3>
|
||||
<p class="footer-text">
|
||||
Your destination for unique art pieces and creative supplies.
|
||||
</p>
|
||||
<div class="social-links">
|
||||
<a href="#" class="social-link"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#" class="social-link"
|
||||
><i class="bi bi-instagram"></i
|
||||
></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-twitter"></i></a>
|
||||
<a href="#" class="social-link"
|
||||
><i class="bi bi-pinterest"></i
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Shop</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shop">All Products</a></li>
|
||||
<li><a href="/shop?category=paintings">Paintings</a></li>
|
||||
<li><a href="/shop?category=prints">Prints</a></li>
|
||||
<li><a href="/shop?category=supplies">Art Supplies</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">About</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/about">Our Story</a></li>
|
||||
<li><a href="/portfolio">Portfolio</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Customer Service</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/privacy">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p id="footerText">© 2025 Sky Art Shop. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
1253
website/public/old-backup/shop.html
Normal file
1253
website/public/old-backup/shop.html
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
260
website/public/privacy-old.html
Normal file
260
website/public/privacy-old.html
Normal file
@@ -0,0 +1,260 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Privacy Policy - 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 rel="stylesheet" href="/assets/css/theme-colors.css" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/page-overrides.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/cart-wishlist.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
<link rel="stylesheet" href="/assets/css/responsive.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script>window.__bodyReady=true</script>
|
||||
<div class="sticky-banner-wrapper">
|
||||
<!-- Modern Navigation -->
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home" class="brand-link">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
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" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio" class="nav-link">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog" class="nav-link active">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact" 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" 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"><i class="bi bi-cart-x"></i><br>Your cart is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<a href="/shop" class="btn-outline">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" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact" class="mobile-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mobile-menu-overlay" id="mobileMenuOverlay"></div>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
// Mobile Menu Toggle
|
||||
(function() {
|
||||
const mobileToggle = document.getElementById('mobileMenuToggle');
|
||||
const mobileMenu = document.getElementById('mobileMenu');
|
||||
const mobileClose = document.getElementById('mobileMenuClose');
|
||||
const overlay = document.getElementById('mobileMenuOverlay');
|
||||
|
||||
function openMenu() {
|
||||
mobileMenu.classList.add('active');
|
||||
overlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
function closeMenu() {
|
||||
mobileMenu.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
if (mobileToggle) mobileToggle.addEventListener('click', openMenu);
|
||||
if (mobileClose) mobileClose.addEventListener('click', closeMenu);
|
||||
if (overlay) overlay.addEventListener('click', closeMenu);
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && mobileMenu.classList.contains('active')) closeMenu();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<section class="about-hero">
|
||||
<div class="container">
|
||||
<h1><i class="bi bi-shield-check"></i> Privacy Policy</h1>
|
||||
<p class="hero-subtitle">How we protect and use your information</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="blog-section" style="padding: 60px 0; background: #ffebeb">
|
||||
<div class="container">
|
||||
<div style="max-width: 800px; margin: 0 auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
|
||||
<p style="color: #666; line-height: 1.8; margin-bottom: 30px;"><em>Last updated: January 14, 2026</em></p>
|
||||
|
||||
<h2 style="color: #333; margin-bottom: 15px;">Information We Collect</h2>
|
||||
<p style="color: #666; line-height: 1.8; margin-bottom: 20px;">We collect information you provide directly to us, including your name, email address, shipping address, and payment information when you make a purchase.</p>
|
||||
|
||||
<h2 style="color: #333; margin-bottom: 15px; margin-top: 30px;">How We Use Your Information</h2>
|
||||
<ul style="color: #666; line-height: 2; margin-left: 20px;">
|
||||
<li>Process and fulfill your orders</li>
|
||||
<li>Send you order confirmations and shipping updates</li>
|
||||
<li>Respond to your questions and provide customer support</li>
|
||||
<li>Send you promotional emails (with your consent)</li>
|
||||
<li>Improve our website and services</li>
|
||||
</ul>
|
||||
|
||||
<h2 style="color: #333; margin-bottom: 15px; margin-top: 30px;">Data Security</h2>
|
||||
<p style="color: #666; line-height: 1.8; margin-bottom: 20px;">We use industry-standard SSL encryption to protect your personal and payment information. We never store credit card numbers on our servers.</p>
|
||||
|
||||
<h2 style="color: #333; margin-bottom: 15px; margin-top: 30px;">Your Rights</h2>
|
||||
<p style="color: #666; line-height: 1.8; margin-bottom: 20px;">You have the right to access, correct, or delete your personal information. You can also unsubscribe from marketing emails at any time.</p>
|
||||
|
||||
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; border-left: 4px solid #667eea; margin-top: 30px;">
|
||||
<p style="color: #666; margin: 0;"><strong>Privacy questions?</strong> Email us at <a href="mailto:privacy@skyartshop.com" style="color: #667eea;">privacy@skyartshop.com</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-grid">
|
||||
<div class="footer-col">
|
||||
<h3 class="footer-title">Sky Art Shop</h3>
|
||||
<p class="footer-text">
|
||||
Your destination for unique art pieces and creative supplies.
|
||||
</p>
|
||||
<div class="social-links">
|
||||
<a href="#" class="social-link"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-instagram"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-twitter"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-pinterest"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Shop</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shop">All Products</a></li>
|
||||
<li><a href="/shop?category=paintings">Paintings</a></li>
|
||||
<li><a href="/shop?category=prints">Prints</a></li>
|
||||
<li><a href="/shop?category=supplies">Art Supplies</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">About</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/about">Our Story</a></li>
|
||||
<li><a href="/portfolio">Portfolio</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Customer Service</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/privacy">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 Sky Art Shop. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Core Scripts (standardized order) -->
|
||||
<script src="/assets/js/main.js"></script>
|
||||
<script src="/assets/js/shop-system.js"></script>
|
||||
<script src="/assets/js/page-transitions.js?v=1766709739"></script>
|
||||
<script src="/assets/js/back-button-control.js?v=1766723554"></script>
|
||||
<script src="/assets/js/navigation.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,260 +1,455 @@
|
||||
<!DOCTYPE html>
|
||||
<!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="Privacy Policy - Sky Art Shop" />
|
||||
<title>Privacy Policy - Sky Art Shop</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<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"
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@600;700;800&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<!-- Icons -->
|
||||
<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/theme-colors.css" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=1735692100" />
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css?v=1768447584" />
|
||||
<link rel="stylesheet" href="/assets/css/page-overrides.css?v=1736790001" />
|
||||
<link rel="stylesheet" href="/assets/css/cart-wishlist.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
<link rel="stylesheet" href="/assets/css/responsive.css" />
|
||||
|
||||
<!-- Modern Theme CSS -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/assets/css/modern-theme.css?v=20260118drawer"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/assets/css/mobile-fixes.css?v=20260118editor"
|
||||
/>
|
||||
|
||||
<style>
|
||||
.policy-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background: var(--bg-white);
|
||||
padding: var(--spacing-2xl);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.policy-container h2 {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 1.75rem;
|
||||
margin-top: var(--spacing-xl);
|
||||
margin-bottom: var(--spacing-md);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.policy-container h2:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.policy-container p {
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.8;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.policy-container ul {
|
||||
color: var(--text-secondary);
|
||||
line-height: 2;
|
||||
margin-left: var(--spacing-xl);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.policy-container ul li {
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.policy-meta {
|
||||
color: var(--text-light);
|
||||
font-style: italic;
|
||||
margin-bottom: var(--spacing-xl);
|
||||
padding: var(--spacing-md);
|
||||
background: var(--primary-pink-light);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.contact-box {
|
||||
background: var(--primary-pink-light);
|
||||
padding: var(--spacing-xl);
|
||||
border-radius: var(--radius-lg);
|
||||
margin-top: var(--spacing-2xl);
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>window.__bodyReady=true</script>
|
||||
<div class="sticky-banner-wrapper">
|
||||
<!-- Modern Navigation -->
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home" class="brand-link">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
alt="Sky Art Shop Logo"
|
||||
class="brand-logo"
|
||||
/>
|
||||
<span class="brand-name">Sky' Art Shop</span>
|
||||
</a>
|
||||
</div>
|
||||
<!-- Navigation -->
|
||||
<header class="nav-wrapper">
|
||||
<nav class="navbar">
|
||||
<a href="/home" class="nav-brand">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
alt="Sky Art Shop Logo"
|
||||
/>
|
||||
<span>Sky Art Shop</span>
|
||||
</a>
|
||||
|
||||
<div class="navbar-menu">
|
||||
<ul class="nav-menu-list">
|
||||
<li class="nav-item">
|
||||
<a href="/home" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio" class="nav-link">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog" class="nav-link active">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact" 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" 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"><i class="bi bi-cart-x"></i><br>Your cart is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<a href="/shop" class="btn-outline">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" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact" class="mobile-link">Contact</a></li>
|
||||
<ul class="nav-menu">
|
||||
<li><a href="/home" class="nav-link">Home</a></li>
|
||||
<li><a href="/shop" class="nav-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="nav-link">Portfolio</a></li>
|
||||
<li><a href="/blog" class="nav-link">Blog</a></li>
|
||||
<li><a href="/about" class="nav-link">About</a></li>
|
||||
<li><a href="/contact" class="nav-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mobile-menu-overlay" id="mobileMenuOverlay"></div>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
// Mobile Menu Toggle
|
||||
(function() {
|
||||
const mobileToggle = document.getElementById('mobileMenuToggle');
|
||||
const mobileMenu = document.getElementById('mobileMenu');
|
||||
const mobileClose = document.getElementById('mobileMenuClose');
|
||||
const overlay = document.getElementById('mobileMenuOverlay');
|
||||
|
||||
function openMenu() {
|
||||
mobileMenu.classList.add('active');
|
||||
overlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
function closeMenu() {
|
||||
mobileMenu.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
if (mobileToggle) mobileToggle.addEventListener('click', openMenu);
|
||||
if (mobileClose) mobileClose.addEventListener('click', closeMenu);
|
||||
if (overlay) overlay.addEventListener('click', closeMenu);
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && mobileMenu.classList.contains('active')) closeMenu();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<section class="about-hero">
|
||||
<div class="container">
|
||||
<h1><i class="bi bi-shield-check"></i> Privacy Policy</h1>
|
||||
<p class="hero-subtitle">How we protect and use your information</p>
|
||||
<div class="nav-actions">
|
||||
<a href="/signin" class="nav-icon-btn" title="Sign In">
|
||||
<i class="bi bi-person"></i>
|
||||
</a>
|
||||
<button class="nav-icon-btn wishlist-btn-nav" title="Wishlist">
|
||||
<i class="bi bi-heart"></i>
|
||||
<span class="badge wishlist-count" style="display: none">0</span>
|
||||
</button>
|
||||
<button class="nav-icon-btn cart-btn" title="Cart">
|
||||
<i class="bi bi-bag"></i>
|
||||
<span class="badge cart-count" style="display: none">0</span>
|
||||
</button>
|
||||
<button class="nav-mobile-toggle" aria-label="Toggle menu">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- Page Content -->
|
||||
<main class="page-content">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1 id="privacyHeaderTitle">
|
||||
<i class="bi bi-shield-check"></i> Privacy Policy
|
||||
</h1>
|
||||
<p>How we protect and use your information</p>
|
||||
<div class="breadcrumb">
|
||||
<a href="/home">Home</a>
|
||||
<span>/</span>
|
||||
<span>Privacy Policy</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="blog-section" style="padding: 60px 0; background: #ffebeb">
|
||||
|
||||
<!-- Policy Content -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="policy-container" id="privacyContainer">
|
||||
<!-- Content will be loaded dynamically from the database -->
|
||||
<div
|
||||
class="loading-placeholder"
|
||||
style="text-align: center; padding: 2rem"
|
||||
>
|
||||
<i
|
||||
class="bi bi-hourglass-split"
|
||||
style="font-size: 2rem; animation: spin 1s linear infinite"
|
||||
></i>
|
||||
<p>Loading privacy policy...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div style="max-width: 800px; margin: 0 auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
|
||||
<p style="color: #666; line-height: 1.8; margin-bottom: 30px;"><em>Last updated: January 14, 2026</em></p>
|
||||
<div class="footer-grid">
|
||||
<div class="footer-about">
|
||||
<div class="footer-brand">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
alt="Sky Art Shop"
|
||||
/>
|
||||
<span>Sky Art Shop</span>
|
||||
</div>
|
||||
<p>
|
||||
Your one-stop shop for scrapbooking, journaling, cardmaking, and
|
||||
collaging stationery. Quality products for all your creative
|
||||
needs.
|
||||
</p>
|
||||
<div class="footer-social">
|
||||
<a href="#" class="social-link"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#" class="social-link"
|
||||
><i class="bi bi-instagram"></i
|
||||
></a>
|
||||
<a href="#" class="social-link"
|
||||
><i class="bi bi-pinterest"></i
|
||||
></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-youtube"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-column">
|
||||
<h4>Quick Links</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/home">Home</a></li>
|
||||
<li><a href="/shop">Shop</a></li>
|
||||
<li><a href="/portfolio">Portfolio</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/about">About Us</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-column">
|
||||
<h4>Customer Service</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns & Refunds</a></li>
|
||||
<li><a href="/privacy">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-column">
|
||||
<h4>Contact Us</h4>
|
||||
<ul class="footer-links">
|
||||
<li><i class="bi bi-envelope"></i> hello@skyartshop.com</li>
|
||||
<li><i class="bi bi-telephone"></i> (555) 123-4567</li>
|
||||
<li><i class="bi bi-geo-alt"></i> 123 Creative Lane</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-bottom">
|
||||
<p>© 2026 Sky Art Shop. All rights reserved.</p>
|
||||
<p>
|
||||
Made with
|
||||
<i class="bi bi-heart-fill" style="color: var(--primary-pink)"></i>
|
||||
for creative souls
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Cart Drawer -->
|
||||
<div class="cart-overlay"></div>
|
||||
<div class="cart-drawer">
|
||||
<div class="cart-header">
|
||||
<h3>Shopping Cart</h3>
|
||||
<button class="cart-close"><i class="bi bi-x"></i></button>
|
||||
</div>
|
||||
<div class="cart-items">
|
||||
<!-- Cart items rendered via JavaScript -->
|
||||
</div>
|
||||
<div class="cart-footer">
|
||||
<div class="cart-total">
|
||||
<span>Total:</span>
|
||||
<span class="cart-total-amount">$0.00</span>
|
||||
</div>
|
||||
<a href="/checkout" class="btn btn-primary cart-checkout-btn"
|
||||
>Checkout</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Wishlist Drawer -->
|
||||
<div class="wishlist-overlay"></div>
|
||||
<div class="wishlist-drawer">
|
||||
<div class="wishlist-header">
|
||||
<h3><i class="bi bi-heart"></i> My Wishlist</h3>
|
||||
<button class="wishlist-close"><i class="bi bi-x"></i></button>
|
||||
</div>
|
||||
<div class="wishlist-items"></div>
|
||||
<div class="wishlist-footer">
|
||||
<a href="/shop" class="btn btn-outline">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="/assets/js/modern-theme.js?v=20260118c"></script>
|
||||
<script src="/assets/js/customer-auth.js"></script>
|
||||
<script src="/assets/js/accessibility.js"></script>
|
||||
<script>
|
||||
// Load privacy page data from API
|
||||
async function loadPrivacyPageData() {
|
||||
try {
|
||||
console.log("Loading privacy page data from API...");
|
||||
const response = await fetch("/api/pages/privacy?" + Date.now(), {
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
"Cache-Control": "no-cache",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.log("Privacy page not found in database, using defaults");
|
||||
renderDefaultPrivacyContent();
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log("Received data from API:", data);
|
||||
const pageData = data.page.pagedata || {};
|
||||
console.log("Page data:", pageData);
|
||||
|
||||
// Update header if data exists
|
||||
if (pageData.header) {
|
||||
if (pageData.header.title) {
|
||||
document.getElementById("privacyHeaderTitle").innerHTML =
|
||||
'<i class="bi bi-shield-check"></i> ' + pageData.header.title;
|
||||
}
|
||||
if (pageData.header.subtitle) {
|
||||
const subtitleEl = document.querySelector(".page-header p");
|
||||
if (subtitleEl) {
|
||||
subtitleEl.textContent = pageData.header.subtitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render content
|
||||
const container = document.getElementById("privacyContainer");
|
||||
let contentHtml = "";
|
||||
|
||||
// Add last updated date if exists
|
||||
if (pageData.lastUpdated) {
|
||||
contentHtml += `<p class="policy-meta">Last updated: ${escapeHtml(pageData.lastUpdated)}</p>`;
|
||||
}
|
||||
|
||||
// Add main content (new format)
|
||||
if (pageData.mainContent) {
|
||||
console.log(
|
||||
"Using mainContent:",
|
||||
pageData.mainContent.substring(0, 100) + "...",
|
||||
);
|
||||
contentHtml += pageData.mainContent;
|
||||
}
|
||||
// Fallback to sections (old format)
|
||||
else if (pageData.sections && pageData.sections.length > 0) {
|
||||
console.log(
|
||||
"Using sections format, sections:",
|
||||
pageData.sections.length,
|
||||
);
|
||||
contentHtml += pageData.sections
|
||||
.map((section) => {
|
||||
let sectionHtml = "";
|
||||
if (section.title) {
|
||||
sectionHtml += `<h2>${escapeHtml(section.title)}</h2>`;
|
||||
}
|
||||
if (section.content) {
|
||||
sectionHtml += section.content;
|
||||
}
|
||||
return sectionHtml;
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
// Add contact box at the end
|
||||
if (pageData.contactBox) {
|
||||
const box = pageData.contactBox;
|
||||
contentHtml += `
|
||||
<div class="contact-box">
|
||||
<h3>${escapeHtml(box.title || "Privacy Questions?")}</h3>
|
||||
<p>${escapeHtml(box.message || "If you have any questions about this Privacy Policy, please contact us:")}</p>
|
||||
<div style="margin-top: var(--spacing-lg)">
|
||||
<p><strong>Email:</strong> ${escapeHtml(box.email || "privacy@skyartshop.com")}</p>
|
||||
<p><strong>Phone:</strong> ${escapeHtml(box.phone || "(555) 123-4567")}</p>
|
||||
<p><strong>Mail:</strong> ${escapeHtml(box.address || "Sky Art Shop, 123 Creative Lane, City, ST 12345")}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
// Fallback to hardcoded contact box (old format)
|
||||
else if (pageData.sections && pageData.sections.length > 0) {
|
||||
contentHtml += `
|
||||
<div class="contact-box">
|
||||
<h3>Privacy Questions?</h3>
|
||||
<p>If you have any questions about this Privacy Policy, please contact us:</p>
|
||||
<div style="margin-top: var(--spacing-lg)">
|
||||
<p><strong>Email:</strong> privacy@skyartshop.com</p>
|
||||
<p><strong>Phone:</strong> (555) 123-4567</p>
|
||||
<p><strong>Mail:</strong> Sky Art Shop, 123 Creative Lane, City, ST 12345</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// If we have any content, show it, otherwise use defaults
|
||||
if (contentHtml.trim()) {
|
||||
container.innerHTML = contentHtml;
|
||||
} else {
|
||||
renderDefaultPrivacyContent();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading privacy page:", error);
|
||||
renderDefaultPrivacyContent();
|
||||
}
|
||||
}
|
||||
|
||||
function renderDefaultPrivacyContent() {
|
||||
const container = document.getElementById("privacyContainer");
|
||||
container.innerHTML = `
|
||||
<p class="policy-meta">Last updated: January 14, 2026</p>
|
||||
|
||||
<h2 style="color: #333; margin-bottom: 15px;">Information We Collect</h2>
|
||||
<p style="color: #666; line-height: 1.8; margin-bottom: 20px;">We collect information you provide directly to us, including your name, email address, shipping address, and payment information when you make a purchase.</p>
|
||||
<p>At Sky Art Shop, we take your privacy seriously. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you visit our website and make purchases from our store.</p>
|
||||
|
||||
<h2 style="color: #333; margin-bottom: 15px; margin-top: 30px;">How We Use Your Information</h2>
|
||||
<ul style="color: #666; line-height: 2; margin-left: 20px;">
|
||||
<h2>Information We Collect</h2>
|
||||
<p>We collect information you provide directly to us when you:</p>
|
||||
<ul>
|
||||
<li>Create an account or make a purchase</li>
|
||||
<li>Subscribe to our newsletter</li>
|
||||
<li>Contact our customer service</li>
|
||||
<li>Participate in surveys or promotions</li>
|
||||
<li>Post reviews or comments</li>
|
||||
</ul>
|
||||
<p>This information may include your name, email address, shipping address, phone number, and payment information.</p>
|
||||
|
||||
<h2>How We Use Your Information</h2>
|
||||
<p>We use the information we collect to:</p>
|
||||
<ul>
|
||||
<li>Process and fulfill your orders</li>
|
||||
<li>Send you order confirmations and shipping updates</li>
|
||||
<li>Respond to your questions and provide customer support</li>
|
||||
<li>Send you promotional emails (with your consent)</li>
|
||||
<li>Improve our website and services</li>
|
||||
<li>Prevent fraud and enhance security</li>
|
||||
<li>Comply with legal obligations</li>
|
||||
</ul>
|
||||
|
||||
<h2 style="color: #333; margin-bottom: 15px; margin-top: 30px;">Data Security</h2>
|
||||
<p style="color: #666; line-height: 1.8; margin-bottom: 20px;">We use industry-standard SSL encryption to protect your personal and payment information. We never store credit card numbers on our servers.</p>
|
||||
<h2>Data Security</h2>
|
||||
<p>We implement industry-standard security measures to protect your personal information:</p>
|
||||
<ul>
|
||||
<li>SSL/TLS encryption for all data transmission</li>
|
||||
<li>Secure payment processing through PCI-compliant providers</li>
|
||||
<li>Regular security audits and updates</li>
|
||||
<li>Limited access to personal information by authorized personnel only</li>
|
||||
</ul>
|
||||
|
||||
<h2 style="color: #333; margin-bottom: 15px; margin-top: 30px;">Your Rights</h2>
|
||||
<p style="color: #666; line-height: 1.8; margin-bottom: 20px;">You have the right to access, correct, or delete your personal information. You can also unsubscribe from marketing emails at any time.</p>
|
||||
|
||||
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; border-left: 4px solid #667eea; margin-top: 30px;">
|
||||
<p style="color: #666; margin: 0;"><strong>Privacy questions?</strong> Email us at <a href="mailto:privacy@skyartshop.com" style="color: #667eea;">privacy@skyartshop.com</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-grid">
|
||||
<div class="footer-col">
|
||||
<h3 class="footer-title">Sky Art Shop</h3>
|
||||
<p class="footer-text">
|
||||
Your destination for unique art pieces and creative supplies.
|
||||
</p>
|
||||
<div class="social-links">
|
||||
<a href="#" class="social-link"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-instagram"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-twitter"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-pinterest"></i></a>
|
||||
<div class="contact-box">
|
||||
<h3>Privacy Questions?</h3>
|
||||
<p>If you have any questions about this Privacy Policy, please contact us:</p>
|
||||
<div style="margin-top: var(--spacing-lg)">
|
||||
<p><strong>Email:</strong> privacy@skyartshop.com</p>
|
||||
<p><strong>Phone:</strong> (555) 123-4567</p>
|
||||
<p><strong>Mail:</strong> Sky Art Shop, 123 Creative Lane, City, ST 12345</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Shop</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shop">All Products</a></li>
|
||||
<li><a href="/shop?category=paintings">Paintings</a></li>
|
||||
<li><a href="/shop?category=prints">Prints</a></li>
|
||||
<li><a href="/shop?category=supplies">Art Supplies</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">About</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/about">Our Story</a></li>
|
||||
<li><a href="/portfolio">Portfolio</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Customer Service</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/privacy">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 Sky Art Shop. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Core Scripts (standardized order) -->
|
||||
<script src="/assets/js/main.js"></script>
|
||||
<script src="/assets/js/shop-system.js"></script>
|
||||
<script src="/assets/js/page-transitions.js?v=1766709739"></script>
|
||||
<script src="/assets/js/back-button-control.js?v=1766723554"></script>
|
||||
<script src="/assets/js/navigation.js"></script>
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Load on page ready
|
||||
document.addEventListener("DOMContentLoaded", loadPrivacyPageData);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
260
website/public/returns-old.html
Normal file
260
website/public/returns-old.html
Normal file
@@ -0,0 +1,260 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Returns & Refunds - 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 rel="stylesheet" href="/assets/css/theme-colors.css" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/page-overrides.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/cart-wishlist.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
<link rel="stylesheet" href="/assets/css/responsive.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script>window.__bodyReady=true</script>
|
||||
<div class="sticky-banner-wrapper">
|
||||
<!-- Modern Navigation -->
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home" class="brand-link">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
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" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio" class="nav-link">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog" class="nav-link active">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact" 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" 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"><i class="bi bi-cart-x"></i><br>Your cart is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<a href="/shop" class="btn-outline">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" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact" class="mobile-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mobile-menu-overlay" id="mobileMenuOverlay"></div>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
// Mobile Menu Toggle
|
||||
(function() {
|
||||
const mobileToggle = document.getElementById('mobileMenuToggle');
|
||||
const mobileMenu = document.getElementById('mobileMenu');
|
||||
const mobileClose = document.getElementById('mobileMenuClose');
|
||||
const overlay = document.getElementById('mobileMenuOverlay');
|
||||
|
||||
function openMenu() {
|
||||
mobileMenu.classList.add('active');
|
||||
overlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
function closeMenu() {
|
||||
mobileMenu.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
if (mobileToggle) mobileToggle.addEventListener('click', openMenu);
|
||||
if (mobileClose) mobileClose.addEventListener('click', closeMenu);
|
||||
if (overlay) overlay.addEventListener('click', closeMenu);
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && mobileMenu.classList.contains('active')) closeMenu();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<section class="about-hero">
|
||||
<div class="container">
|
||||
<h1><i class="bi bi-arrow-left-right"></i> Returns & Refunds</h1>
|
||||
<p class="hero-subtitle">30-day hassle-free return policy</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="blog-section" style="padding: 60px 0; background: #ffebeb">
|
||||
<div class="container">
|
||||
<div style="max-width: 800px; margin: 0 auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
|
||||
<h2 style="color: #333; margin-bottom: 20px;"><i class="bi bi-check-circle" style="color: #667eea;"></i> 30-Day Return Policy</h2>
|
||||
<p style="color: #666; line-height: 1.8; margin-bottom: 20px;">We want you to love your purchase! If you're not completely satisfied, you can return most items within 30 days of delivery for a full refund.</p>
|
||||
|
||||
<h3 style="color: #667eea; font-size: 18px; margin-top: 30px;">How to Return an Item</h3>
|
||||
<ol style="color: #666; line-height: 2; margin-left: 20px;">
|
||||
<li>Contact our customer service team to initiate a return</li>
|
||||
<li>Pack the item securely in its original packaging</li>
|
||||
<li>Include your order number and reason for return</li>
|
||||
<li>Ship the package using our prepaid return label</li>
|
||||
<li>Refund will be processed within 5-7 business days after we receive the item</li>
|
||||
</ol>
|
||||
|
||||
<h3 style="color: #667eea; font-size: 18px; margin-top: 30px;">Return Requirements</h3>
|
||||
<ul style="color: #666; line-height: 2; margin-left: 20px;">
|
||||
<li>Item must be unused and in original condition</li>
|
||||
<li>Original packaging and tags must be intact</li>
|
||||
<li>Custom or personalized items cannot be returned</li>
|
||||
<li>Sale items are final sale unless defective</li>
|
||||
</ul>
|
||||
|
||||
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; border-left: 4px solid #667eea; margin-top: 30px;">
|
||||
<p style="color: #666; margin: 0;"><strong>Questions about returns?</strong> Contact us at <a href="mailto:returns@skyartshop.com" style="color: #667eea;">returns@skyartshop.com</a> or call 1-800-SKY-ARTS</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-grid">
|
||||
<div class="footer-col">
|
||||
<h3 class="footer-title">Sky Art Shop</h3>
|
||||
<p class="footer-text">
|
||||
Your destination for unique art pieces and creative supplies.
|
||||
</p>
|
||||
<div class="social-links">
|
||||
<a href="#" class="social-link"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-instagram"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-twitter"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-pinterest"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Shop</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shop">All Products</a></li>
|
||||
<li><a href="/shop?category=paintings">Paintings</a></li>
|
||||
<li><a href="/shop?category=prints">Prints</a></li>
|
||||
<li><a href="/shop?category=supplies">Art Supplies</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">About</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/about">Our Story</a></li>
|
||||
<li><a href="/portfolio">Portfolio</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Customer Service</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/privacy">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 Sky Art Shop. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Core Scripts (standardized order) -->
|
||||
<script src="/assets/js/main.js"></script>
|
||||
<script src="/assets/js/shop-system.js"></script>
|
||||
<script src="/assets/js/page-transitions.js?v=1766709739"></script>
|
||||
<script src="/assets/js/back-button-control.js?v=1766723554"></script>
|
||||
<script src="/assets/js/navigation.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,260 +1,400 @@
|
||||
<!DOCTYPE html>
|
||||
<!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="Returns & Refunds Policy - Sky Art Shop"
|
||||
/>
|
||||
<title>Returns & Refunds - Sky Art Shop</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<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"
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@600;700;800&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<!-- Icons -->
|
||||
<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/theme-colors.css" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=1735692100" />
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css?v=1768447584" />
|
||||
<link rel="stylesheet" href="/assets/css/page-overrides.css?v=1736790001" />
|
||||
<link rel="stylesheet" href="/assets/css/cart-wishlist.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
<link rel="stylesheet" href="/assets/css/responsive.css" />
|
||||
|
||||
<!-- Modern Theme CSS -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/assets/css/modern-theme.css?v=20260118drawer"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/assets/css/mobile-fixes.css?v=20260118editor"
|
||||
/>
|
||||
|
||||
<style>
|
||||
.policy-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background: var(--bg-white);
|
||||
padding: var(--spacing-2xl);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.policy-container h2 {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 1.75rem;
|
||||
margin-top: var(--spacing-xl);
|
||||
margin-bottom: var(--spacing-md);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.policy-container h2:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.policy-container p {
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.8;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.policy-container ul,
|
||||
.policy-container ol {
|
||||
color: var(--text-secondary);
|
||||
line-height: 2;
|
||||
margin-left: var(--spacing-xl);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.policy-container ul li,
|
||||
.policy-container ol li {
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.highlight-box {
|
||||
background: var(--primary-pink-light);
|
||||
padding: var(--spacing-lg);
|
||||
border-radius: var(--radius-md);
|
||||
margin: var(--spacing-lg) 0;
|
||||
border-left: 4px solid var(--primary-pink-dark);
|
||||
}
|
||||
|
||||
.steps-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: var(--spacing-lg);
|
||||
margin: var(--spacing-xl) 0;
|
||||
}
|
||||
|
||||
.step-card {
|
||||
text-align: center;
|
||||
padding: var(--spacing-lg);
|
||||
background: var(--bg-light);
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin: 0 auto var(--spacing-md);
|
||||
background: var(--primary-pink-dark);
|
||||
color: var(--text-white);
|
||||
border-radius: var(--radius-full);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>window.__bodyReady=true</script>
|
||||
<div class="sticky-banner-wrapper">
|
||||
<!-- Modern Navigation -->
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home" class="brand-link">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
alt="Sky Art Shop Logo"
|
||||
class="brand-logo"
|
||||
/>
|
||||
<span class="brand-name">Sky' Art Shop</span>
|
||||
</a>
|
||||
</div>
|
||||
<!-- Navigation -->
|
||||
<header class="nav-wrapper">
|
||||
<nav class="navbar">
|
||||
<a href="/home" class="nav-brand">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
alt="Sky Art Shop Logo"
|
||||
/>
|
||||
<span>Sky Art Shop</span>
|
||||
</a>
|
||||
|
||||
<div class="navbar-menu">
|
||||
<ul class="nav-menu-list">
|
||||
<li class="nav-item">
|
||||
<a href="/home" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio" class="nav-link">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog" class="nav-link active">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact" 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" 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"><i class="bi bi-cart-x"></i><br>Your cart is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<a href="/shop" class="btn-outline">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" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact" class="mobile-link">Contact</a></li>
|
||||
<ul class="nav-menu">
|
||||
<li><a href="/home" class="nav-link">Home</a></li>
|
||||
<li><a href="/shop" class="nav-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="nav-link">Portfolio</a></li>
|
||||
<li><a href="/blog" class="nav-link">Blog</a></li>
|
||||
<li><a href="/about" class="nav-link">About</a></li>
|
||||
<li><a href="/contact" class="nav-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mobile-menu-overlay" id="mobileMenuOverlay"></div>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
// Mobile Menu Toggle
|
||||
(function() {
|
||||
const mobileToggle = document.getElementById('mobileMenuToggle');
|
||||
const mobileMenu = document.getElementById('mobileMenu');
|
||||
const mobileClose = document.getElementById('mobileMenuClose');
|
||||
const overlay = document.getElementById('mobileMenuOverlay');
|
||||
|
||||
function openMenu() {
|
||||
mobileMenu.classList.add('active');
|
||||
overlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
function closeMenu() {
|
||||
mobileMenu.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
if (mobileToggle) mobileToggle.addEventListener('click', openMenu);
|
||||
if (mobileClose) mobileClose.addEventListener('click', closeMenu);
|
||||
if (overlay) overlay.addEventListener('click', closeMenu);
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && mobileMenu.classList.contains('active')) closeMenu();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<section class="about-hero">
|
||||
<div class="container">
|
||||
<h1><i class="bi bi-arrow-left-right"></i> Returns & Refunds</h1>
|
||||
<p class="hero-subtitle">30-day hassle-free return policy</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="blog-section" style="padding: 60px 0; background: #ffebeb">
|
||||
<div class="container">
|
||||
<div style="max-width: 800px; margin: 0 auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
|
||||
<h2 style="color: #333; margin-bottom: 20px;"><i class="bi bi-check-circle" style="color: #667eea;"></i> 30-Day Return Policy</h2>
|
||||
<p style="color: #666; line-height: 1.8; margin-bottom: 20px;">We want you to love your purchase! If you're not completely satisfied, you can return most items within 30 days of delivery for a full refund.</p>
|
||||
|
||||
<h3 style="color: #667eea; font-size: 18px; margin-top: 30px;">How to Return an Item</h3>
|
||||
<ol style="color: #666; line-height: 2; margin-left: 20px;">
|
||||
<li>Contact our customer service team to initiate a return</li>
|
||||
<li>Pack the item securely in its original packaging</li>
|
||||
<li>Include your order number and reason for return</li>
|
||||
<li>Ship the package using our prepaid return label</li>
|
||||
<li>Refund will be processed within 5-7 business days after we receive the item</li>
|
||||
</ol>
|
||||
|
||||
<h3 style="color: #667eea; font-size: 18px; margin-top: 30px;">Return Requirements</h3>
|
||||
<ul style="color: #666; line-height: 2; margin-left: 20px;">
|
||||
<li>Item must be unused and in original condition</li>
|
||||
<li>Original packaging and tags must be intact</li>
|
||||
<li>Custom or personalized items cannot be returned</li>
|
||||
<li>Sale items are final sale unless defective</li>
|
||||
</ul>
|
||||
|
||||
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; border-left: 4px solid #667eea; margin-top: 30px;">
|
||||
<p style="color: #666; margin: 0;"><strong>Questions about returns?</strong> Contact us at <a href="mailto:returns@skyartshop.com" style="color: #667eea;">returns@skyartshop.com</a> or call 1-800-SKY-ARTS</p>
|
||||
<div class="nav-actions">
|
||||
<a href="/signin" class="nav-icon-btn" title="Sign In">
|
||||
<i class="bi bi-person"></i>
|
||||
</a>
|
||||
<button class="nav-icon-btn wishlist-btn-nav" title="Wishlist">
|
||||
<i class="bi bi-heart"></i>
|
||||
<span class="badge wishlist-count" style="display: none">0</span>
|
||||
</button>
|
||||
<button class="nav-icon-btn cart-btn" title="Cart">
|
||||
<i class="bi bi-bag"></i>
|
||||
<span class="badge cart-count" style="display: none">0</span>
|
||||
</button>
|
||||
<button class="nav-mobile-toggle" aria-label="Toggle menu">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- Page Content -->
|
||||
<main class="page-content">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1 id="returnsHeaderTitle">
|
||||
<i class="bi bi-arrow-return-left"></i> Returns & Refunds
|
||||
</h1>
|
||||
<p id="returnsHeaderSubtitle">Our hassle-free return policy</p>
|
||||
<div class="breadcrumb">
|
||||
<a href="/home">Home</a>
|
||||
<span>/</span>
|
||||
<span>Returns & Refunds</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Policy Content -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="policy-container" id="returnsContainer">
|
||||
<div class="loading-spinner" style="margin: 40px auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-grid">
|
||||
<div class="footer-col">
|
||||
<h3 class="footer-title">Sky Art Shop</h3>
|
||||
<p class="footer-text">
|
||||
Your destination for unique art pieces and creative supplies.
|
||||
<div class="footer-about">
|
||||
<div class="footer-brand">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
alt="Sky Art Shop"
|
||||
/>
|
||||
<span>Sky Art Shop</span>
|
||||
</div>
|
||||
<p>
|
||||
Your one-stop shop for scrapbooking, journaling, cardmaking, and
|
||||
collaging stationery. Quality products for all your creative
|
||||
needs.
|
||||
</p>
|
||||
<div class="social-links">
|
||||
<div class="footer-social">
|
||||
<a href="#" class="social-link"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-instagram"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-twitter"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-pinterest"></i></a>
|
||||
<a href="#" class="social-link"
|
||||
><i class="bi bi-instagram"></i
|
||||
></a>
|
||||
<a href="#" class="social-link"
|
||||
><i class="bi bi-pinterest"></i
|
||||
></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-youtube"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Shop</h4>
|
||||
|
||||
<div class="footer-column">
|
||||
<h4>Quick Links</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shop">All Products</a></li>
|
||||
<li><a href="/shop?category=paintings">Paintings</a></li>
|
||||
<li><a href="/shop?category=prints">Prints</a></li>
|
||||
<li><a href="/shop?category=supplies">Art Supplies</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">About</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/about">Our Story</a></li>
|
||||
<li><a href="/home">Home</a></li>
|
||||
<li><a href="/shop">Shop</a></li>
|
||||
<li><a href="/portfolio">Portfolio</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/about">About Us</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Customer Service</h4>
|
||||
|
||||
<div class="footer-column">
|
||||
<h4>Customer Service</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns & Refunds</a></li>
|
||||
<li><a href="/privacy">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-column">
|
||||
<h4>Contact Us</h4>
|
||||
<ul class="footer-links">
|
||||
<li><i class="bi bi-envelope"></i> hello@skyartshop.com</li>
|
||||
<li><i class="bi bi-telephone"></i> (555) 123-4567</li>
|
||||
<li><i class="bi bi-geo-alt"></i> 123 Creative Lane</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 Sky Art Shop. All rights reserved.</p>
|
||||
<p>© 2026 Sky Art Shop. All rights reserved.</p>
|
||||
<p>
|
||||
Made with
|
||||
<i class="bi bi-heart-fill" style="color: var(--primary-pink)"></i>
|
||||
for creative souls
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Core Scripts (standardized order) -->
|
||||
<script src="/assets/js/main.js"></script>
|
||||
<script src="/assets/js/shop-system.js"></script>
|
||||
<script src="/assets/js/page-transitions.js?v=1766709739"></script>
|
||||
<script src="/assets/js/back-button-control.js?v=1766723554"></script>
|
||||
<script src="/assets/js/navigation.js"></script>
|
||||
|
||||
|
||||
<!-- Cart Drawer -->
|
||||
<div class="cart-overlay"></div>
|
||||
<div class="cart-drawer">
|
||||
<div class="cart-header">
|
||||
<h3>Shopping Cart</h3>
|
||||
<button class="cart-close"><i class="bi bi-x"></i></button>
|
||||
</div>
|
||||
<div class="cart-items">
|
||||
<!-- Cart items rendered via JavaScript -->
|
||||
</div>
|
||||
<div class="cart-footer">
|
||||
<div class="cart-total">
|
||||
<span>Total:</span>
|
||||
<span class="cart-total-amount">$0.00</span>
|
||||
</div>
|
||||
<a href="/checkout" class="btn btn-primary cart-checkout-btn"
|
||||
>Checkout</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Wishlist Drawer -->
|
||||
<div class="wishlist-overlay"></div>
|
||||
<div class="wishlist-drawer">
|
||||
<div class="wishlist-header">
|
||||
<h3><i class="bi bi-heart"></i> My Wishlist</h3>
|
||||
<button class="wishlist-close"><i class="bi bi-x"></i></button>
|
||||
</div>
|
||||
<div class="wishlist-items"></div>
|
||||
<div class="wishlist-footer">
|
||||
<a href="/shop" class="btn btn-outline">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="/assets/js/modern-theme.js?v=20260118c"></script>
|
||||
<script src="/assets/js/customer-auth.js"></script>
|
||||
<script>
|
||||
// Load returns page data from API
|
||||
async function loadReturnsPageData() {
|
||||
const container = document.getElementById("returnsContainer");
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/pages/returns");
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.page && data.page.pagedata) {
|
||||
const pagedata =
|
||||
typeof data.page.pagedata === "string"
|
||||
? JSON.parse(data.page.pagedata)
|
||||
: data.page.pagedata;
|
||||
|
||||
// Update header
|
||||
if (pagedata.header) {
|
||||
if (pagedata.header.title) {
|
||||
document.getElementById("returnsHeaderTitle").innerHTML =
|
||||
'<i class="bi bi-arrow-return-left"></i> ' +
|
||||
pagedata.header.title;
|
||||
}
|
||||
if (pagedata.header.subtitle) {
|
||||
document.getElementById("returnsHeaderSubtitle").textContent =
|
||||
pagedata.header.subtitle;
|
||||
}
|
||||
}
|
||||
|
||||
// Build content HTML
|
||||
let html = "";
|
||||
|
||||
// Highlight box
|
||||
if (pagedata.highlight) {
|
||||
html += `
|
||||
<div class="highlight-box">
|
||||
<p style="margin: 0; font-weight: 600; font-size: 1.1rem">
|
||||
<i class="bi bi-info-circle"></i> ${pagedata.highlight}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Render sections
|
||||
if (pagedata.sections && pagedata.sections.length > 0) {
|
||||
pagedata.sections.forEach((section) => {
|
||||
html += `<h2>${escapeHtml(section.title)}</h2>`;
|
||||
html += `<div>${section.content}</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
// Contact box
|
||||
html += `
|
||||
<div class="highlight-box">
|
||||
<h3 style="margin-top: 0">Need Help?</h3>
|
||||
<p style="margin-bottom: var(--spacing-md)">
|
||||
Our customer service team is here to assist you with any return questions.
|
||||
</p>
|
||||
<div style="display: flex; gap: var(--spacing-md); flex-wrap: wrap">
|
||||
<a href="/contact" class="btn btn-primary">Contact Us</a>
|
||||
<a href="mailto:support@skyartshop.com" class="btn btn-outline">Email Support</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.innerHTML = html;
|
||||
} else {
|
||||
// Show default content if no API data
|
||||
showDefaultContent(container);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading returns page:", error);
|
||||
showDefaultContent(container);
|
||||
}
|
||||
}
|
||||
|
||||
function showDefaultContent(container) {
|
||||
container.innerHTML = `
|
||||
<div class="highlight-box">
|
||||
<p style="margin: 0; font-weight: 600; font-size: 1.1rem">
|
||||
<i class="bi bi-info-circle"></i> We want you to love your purchase!
|
||||
If you're not completely satisfied, we offer a 30-day return policy on most items.
|
||||
</p>
|
||||
</div>
|
||||
<h2>Return Policy</h2>
|
||||
<p>Please contact us for our full return policy details.</p>
|
||||
<div class="highlight-box">
|
||||
<h3 style="margin-top: 0">Need Help?</h3>
|
||||
<p style="margin-bottom: var(--spacing-md)">
|
||||
Our customer service team is here to assist you.
|
||||
</p>
|
||||
<div style="display: flex; gap: var(--spacing-md); flex-wrap: wrap">
|
||||
<a href="/contact" class="btn btn-primary">Contact Us</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
if (!text) return "";
|
||||
const div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", loadReturnsPageData);
|
||||
</script>
|
||||
<script src="/assets/js/accessibility.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
253
website/public/shipping-info-old.html
Normal file
253
website/public/shipping-info-old.html
Normal file
@@ -0,0 +1,253 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Shipping Information - 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 rel="stylesheet" href="/assets/css/theme-colors.css" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/page-overrides.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/cart-wishlist.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
<link rel="stylesheet" href="/assets/css/responsive.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script>window.__bodyReady=true</script>
|
||||
<div class="sticky-banner-wrapper">
|
||||
<!-- Modern Navigation -->
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home" class="brand-link">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
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" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio" class="nav-link">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog" class="nav-link active">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact" 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" 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"><i class="bi bi-cart-x"></i><br>Your cart is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<a href="/shop" class="btn-outline">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" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact" class="mobile-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mobile-menu-overlay" id="mobileMenuOverlay"></div>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
// Mobile Menu Toggle
|
||||
(function() {
|
||||
const mobileToggle = document.getElementById('mobileMenuToggle');
|
||||
const mobileMenu = document.getElementById('mobileMenu');
|
||||
const mobileClose = document.getElementById('mobileMenuClose');
|
||||
const overlay = document.getElementById('mobileMenuOverlay');
|
||||
|
||||
function openMenu() {
|
||||
mobileMenu.classList.add('active');
|
||||
overlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
function closeMenu() {
|
||||
mobileMenu.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
if (mobileToggle) mobileToggle.addEventListener('click', openMenu);
|
||||
if (mobileClose) mobileClose.addEventListener('click', closeMenu);
|
||||
if (overlay) overlay.addEventListener('click', closeMenu);
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && mobileMenu.classList.contains('active')) closeMenu();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<section class="about-hero">
|
||||
<div class="container">
|
||||
<h1><i class="bi bi-truck"></i> Shipping Information</h1>
|
||||
<p class="hero-subtitle">Fast, reliable delivery to your doorstep</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="blog-section" style="padding: 60px 0; background: #ffebeb">
|
||||
<div class="container">
|
||||
<div style="max-width: 800px; margin: 0 auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 2 8px rgba(0,0,0,0.1);">
|
||||
<h2 style="color: #333; margin-bottom: 20px;"><i class="bi bi-box-seam" style="color: #667eea;"></i> Shipping Methods</h2>
|
||||
<div style="margin-bottom: 30px;">
|
||||
<h3 style="color: #667eea; font-size: 18px;">Standard Shipping (5-7 Business Days)</h3>
|
||||
<p style="color: #666; line-height: 1.8;">Our most economical shipping option. Orders are processed within 1-2 business days.</p>
|
||||
<p style="color: #999; font-size: 14px;"><strong>Cost:</strong> $5.99 for orders under $50 | <strong>FREE</strong> for orders over $50</p>
|
||||
</div>
|
||||
<div style="margin-bottom: 30px;">
|
||||
<h3 style="color: #667eea; font-size: 18px;">Express Shipping (2-3 Business Days)</h3>
|
||||
<p style="color: #666; line-height: 1.8;">Faster delivery via UPS 2nd Day Air.</p>
|
||||
<p style="color: #999; font-size: 14px;"><strong>Cost:</strong> $12.99 flat rate</p>
|
||||
</div>
|
||||
<div style="margin-bottom: 30px;">
|
||||
<h3 style="color: #667eea; font-size: 18px;">Overnight Shipping</h3>
|
||||
<p style="color: #666; line-height: 1.8;">Order by 2 PM EST for next business day delivery.</p>
|
||||
<p style="color: #999; font-size: 14px;"><strong>Cost:</strong> $24.99 flat rate</p>
|
||||
</div>
|
||||
<h2 style="color: #333; margin-bottom: 20px; margin-top: 40px;"><i class="bi bi-globe" style="color: #667eea;"></i> International Shipping</h2>
|
||||
<p style="color: #666; line-height: 1.8;">We ship to over 50 countries worldwide! International orders typically take 7-14 business days. Customs fees are the responsibility of the customer.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-grid">
|
||||
<div class="footer-col">
|
||||
<h3 class="footer-title">Sky Art Shop</h3>
|
||||
<p class="footer-text">
|
||||
Your destination for unique art pieces and creative supplies.
|
||||
</p>
|
||||
<div class="social-links">
|
||||
<a href="#" class="social-link"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-instagram"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-twitter"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-pinterest"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Shop</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shop">All Products</a></li>
|
||||
<li><a href="/shop?category=paintings">Paintings</a></li>
|
||||
<li><a href="/shop?category=prints">Prints</a></li>
|
||||
<li><a href="/shop?category=supplies">Art Supplies</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">About</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/about">Our Story</a></li>
|
||||
<li><a href="/portfolio">Portfolio</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Customer Service</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/privacy">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 Sky Art Shop. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Core Scripts (standardized order) -->
|
||||
<script src="/assets/js/main.js"></script>
|
||||
<script src="/assets/js/shop-system.js"></script>
|
||||
<script src="/assets/js/page-transitions.js?v=1766709739"></script>
|
||||
<script src="/assets/js/back-button-control.js?v=1766723554"></script>
|
||||
<script src="/assets/js/navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,253 +1,476 @@
|
||||
<!DOCTYPE html>
|
||||
<!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="Shipping Information - Sky Art Shop" />
|
||||
<title>Shipping Information - Sky Art Shop</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<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"
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@600;700;800&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<!-- Icons -->
|
||||
<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/theme-colors.css" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=1735692100" />
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css?v=1768447584" />
|
||||
<link rel="stylesheet" href="/assets/css/page-overrides.css?v=1736790001" />
|
||||
<link rel="stylesheet" href="/assets/css/cart-wishlist.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
<link rel="stylesheet" href="/assets/css/responsive.css" />
|
||||
|
||||
<!-- Modern Theme CSS -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/assets/css/modern-theme.css?v=20260118drawer"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/assets/css/mobile-fixes.css?v=20260118editor"
|
||||
/>
|
||||
|
||||
<style>
|
||||
.policy-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background: var(--bg-white);
|
||||
padding: var(--spacing-2xl);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.policy-container h2 {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 1.75rem;
|
||||
margin-top: var(--spacing-xl);
|
||||
margin-bottom: var(--spacing-md);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.policy-container h2:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.policy-container p {
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.8;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.policy-container ul {
|
||||
color: var(--text-secondary);
|
||||
line-height: 2;
|
||||
margin-left: var(--spacing-xl);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.policy-container ul li {
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.shipping-options {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: var(--spacing-lg);
|
||||
margin: var(--spacing-xl) 0;
|
||||
}
|
||||
|
||||
.shipping-card {
|
||||
background: var(--bg-light);
|
||||
padding: var(--spacing-xl);
|
||||
border-radius: var(--radius-lg);
|
||||
text-align: center;
|
||||
border: 2px solid var(--border-light);
|
||||
transition: var(--transition-smooth);
|
||||
}
|
||||
|
||||
.shipping-card:hover {
|
||||
border-color: var(--primary-pink-dark);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.shipping-icon {
|
||||
font-size: 3rem;
|
||||
color: var(--primary-pink-dark);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.shipping-card h3 {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.shipping-card .price {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin: var(--spacing-md) 0;
|
||||
}
|
||||
|
||||
.highlight-box {
|
||||
background: var(--primary-pink-light);
|
||||
padding: var(--spacing-lg);
|
||||
border-radius: var(--radius-md);
|
||||
margin: var(--spacing-lg) 0;
|
||||
border-left: 4px solid var(--primary-pink-dark);
|
||||
}
|
||||
|
||||
.info-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: var(--spacing-lg) 0;
|
||||
}
|
||||
|
||||
.info-table th,
|
||||
.info-table td {
|
||||
padding: var(--spacing-md);
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.info-table th {
|
||||
background: var(--bg-light);
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.info-table td {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>window.__bodyReady=true</script>
|
||||
<div class="sticky-banner-wrapper">
|
||||
<!-- Modern Navigation -->
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home" class="brand-link">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
alt="Sky Art Shop Logo"
|
||||
class="brand-logo"
|
||||
/>
|
||||
<span class="brand-name">Sky' Art Shop</span>
|
||||
</a>
|
||||
</div>
|
||||
<!-- Navigation -->
|
||||
<header class="nav-wrapper">
|
||||
<nav class="navbar">
|
||||
<a href="/home" class="nav-brand">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
alt="Sky Art Shop Logo"
|
||||
/>
|
||||
<span>Sky Art Shop</span>
|
||||
</a>
|
||||
|
||||
<div class="navbar-menu">
|
||||
<ul class="nav-menu-list">
|
||||
<li class="nav-item">
|
||||
<a href="/home" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/shop" class="nav-link">Shop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/portfolio" class="nav-link">Portfolio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/about" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/blog" class="nav-link active">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/contact" 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" 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"><i class="bi bi-cart-x"></i><br>Your cart is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<a href="/shop" class="btn-outline">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" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact" class="mobile-link">Contact</a></li>
|
||||
<ul class="nav-menu">
|
||||
<li><a href="/home" class="nav-link">Home</a></li>
|
||||
<li><a href="/shop" class="nav-link">Shop</a></li>
|
||||
<li><a href="/portfolio" class="nav-link">Portfolio</a></li>
|
||||
<li><a href="/blog" class="nav-link">Blog</a></li>
|
||||
<li><a href="/about" class="nav-link">About</a></li>
|
||||
<li><a href="/contact" class="nav-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mobile-menu-overlay" id="mobileMenuOverlay"></div>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
// Mobile Menu Toggle
|
||||
(function() {
|
||||
const mobileToggle = document.getElementById('mobileMenuToggle');
|
||||
const mobileMenu = document.getElementById('mobileMenu');
|
||||
const mobileClose = document.getElementById('mobileMenuClose');
|
||||
const overlay = document.getElementById('mobileMenuOverlay');
|
||||
|
||||
function openMenu() {
|
||||
mobileMenu.classList.add('active');
|
||||
overlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
function closeMenu() {
|
||||
mobileMenu.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
if (mobileToggle) mobileToggle.addEventListener('click', openMenu);
|
||||
if (mobileClose) mobileClose.addEventListener('click', closeMenu);
|
||||
if (overlay) overlay.addEventListener('click', closeMenu);
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && mobileMenu.classList.contains('active')) closeMenu();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<section class="about-hero">
|
||||
<div class="container">
|
||||
<h1><i class="bi bi-truck"></i> Shipping Information</h1>
|
||||
<p class="hero-subtitle">Fast, reliable delivery to your doorstep</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="blog-section" style="padding: 60px 0; background: #ffebeb">
|
||||
<div class="container">
|
||||
<div style="max-width: 800px; margin: 0 auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 2 8px rgba(0,0,0,0.1);">
|
||||
<h2 style="color: #333; margin-bottom: 20px;"><i class="bi bi-box-seam" style="color: #667eea;"></i> Shipping Methods</h2>
|
||||
<div style="margin-bottom: 30px;">
|
||||
<h3 style="color: #667eea; font-size: 18px;">Standard Shipping (5-7 Business Days)</h3>
|
||||
<p style="color: #666; line-height: 1.8;">Our most economical shipping option. Orders are processed within 1-2 business days.</p>
|
||||
<p style="color: #999; font-size: 14px;"><strong>Cost:</strong> $5.99 for orders under $50 | <strong>FREE</strong> for orders over $50</p>
|
||||
<div class="nav-actions">
|
||||
<a href="/signin" class="nav-icon-btn" title="Sign In">
|
||||
<i class="bi bi-person"></i>
|
||||
</a>
|
||||
<button class="nav-icon-btn wishlist-btn-nav" title="Wishlist">
|
||||
<i class="bi bi-heart"></i>
|
||||
<span class="badge wishlist-count" style="display: none">0</span>
|
||||
</button>
|
||||
<button class="nav-icon-btn cart-btn" title="Cart">
|
||||
<i class="bi bi-bag"></i>
|
||||
<span class="badge cart-count" style="display: none">0</span>
|
||||
</button>
|
||||
<button class="nav-mobile-toggle" aria-label="Toggle menu">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- Page Content -->
|
||||
<main class="page-content">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1 id="shippingHeaderTitle">
|
||||
<i class="bi bi-truck"></i> Shipping Information
|
||||
</h1>
|
||||
<p id="shippingHeaderSubtitle">Fast and reliable shipping options</p>
|
||||
<div class="breadcrumb">
|
||||
<a href="/home">Home</a>
|
||||
<span>/</span>
|
||||
<span>Shipping Information</span>
|
||||
</div>
|
||||
<div style="margin-bottom: 30px;">
|
||||
<h3 style="color: #667eea; font-size: 18px;">Express Shipping (2-3 Business Days)</h3>
|
||||
<p style="color: #666; line-height: 1.8;">Faster delivery via UPS 2nd Day Air.</p>
|
||||
<p style="color: #999; font-size: 14px;"><strong>Cost:</strong> $12.99 flat rate</p>
|
||||
</div>
|
||||
<div style="margin-bottom: 30px;">
|
||||
<h3 style="color: #667eea; font-size: 18px;">Overnight Shipping</h3>
|
||||
<p style="color: #666; line-height: 1.8;">Order by 2 PM EST for next business day delivery.</p>
|
||||
<p style="color: #999; font-size: 14px;"><strong>Cost:</strong> $24.99 flat rate</p>
|
||||
</div>
|
||||
<h2 style="color: #333; margin-bottom: 20px; margin-top: 40px;"><i class="bi bi-globe" style="color: #667eea;"></i> International Shipping</h2>
|
||||
<p style="color: #666; line-height: 1.8;">We ship to over 50 countries worldwide! International orders typically take 7-14 business days. Customs fees are the responsibility of the customer.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Shipping Content -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="policy-container" id="shippingContainer">
|
||||
<!-- Content will be loaded dynamically from the database -->
|
||||
<div
|
||||
class="loading-placeholder"
|
||||
style="text-align: center; padding: 2rem"
|
||||
>
|
||||
<i
|
||||
class="bi bi-hourglass-split"
|
||||
style="font-size: 2rem; animation: spin 1s linear infinite"
|
||||
></i>
|
||||
<p>Loading shipping information...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-grid">
|
||||
<div class="footer-col">
|
||||
<h3 class="footer-title">Sky Art Shop</h3>
|
||||
<p class="footer-text">
|
||||
Your destination for unique art pieces and creative supplies.
|
||||
<div class="footer-about">
|
||||
<div class="footer-brand">
|
||||
<img
|
||||
src="/uploads/cat-png-1767324141436-368259437.png"
|
||||
alt="Sky Art Shop"
|
||||
/>
|
||||
<span>Sky Art Shop</span>
|
||||
</div>
|
||||
<p>
|
||||
Your one-stop shop for scrapbooking, journaling, cardmaking, and
|
||||
collaging stationery. Quality products for all your creative
|
||||
needs.
|
||||
</p>
|
||||
<div class="social-links">
|
||||
<div class="footer-social">
|
||||
<a href="#" class="social-link"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-instagram"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-twitter"></i></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-pinterest"></i></a>
|
||||
<a href="#" class="social-link"
|
||||
><i class="bi bi-instagram"></i
|
||||
></a>
|
||||
<a href="#" class="social-link"
|
||||
><i class="bi bi-pinterest"></i
|
||||
></a>
|
||||
<a href="#" class="social-link"><i class="bi bi-youtube"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Shop</h4>
|
||||
|
||||
<div class="footer-column">
|
||||
<h4>Quick Links</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shop">All Products</a></li>
|
||||
<li><a href="/shop?category=paintings">Paintings</a></li>
|
||||
<li><a href="/shop?category=prints">Prints</a></li>
|
||||
<li><a href="/shop?category=supplies">Art Supplies</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">About</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/about">Our Story</a></li>
|
||||
<li><a href="/home">Home</a></li>
|
||||
<li><a href="/shop">Shop</a></li>
|
||||
<li><a href="/portfolio">Portfolio</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/about">About Us</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Customer Service</h4>
|
||||
|
||||
<div class="footer-column">
|
||||
<h4>Customer Service</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/shipping-info">Shipping Info</a></li>
|
||||
<li><a href="/returns">Returns & Refunds</a></li>
|
||||
<li><a href="/privacy">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-column">
|
||||
<h4>Contact Us</h4>
|
||||
<ul class="footer-links">
|
||||
<li><i class="bi bi-envelope"></i> hello@skyartshop.com</li>
|
||||
<li><i class="bi bi-telephone"></i> (555) 123-4567</li>
|
||||
<li><i class="bi bi-geo-alt"></i> 123 Creative Lane</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 Sky Art Shop. All rights reserved.</p>
|
||||
<p>© 2026 Sky Art Shop. All rights reserved.</p>
|
||||
<p>
|
||||
Made with
|
||||
<i class="bi bi-heart-fill" style="color: var(--primary-pink)"></i>
|
||||
for creative souls
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Core Scripts (standardized order) -->
|
||||
<script src="/assets/js/main.js"></script>
|
||||
<script src="/assets/js/shop-system.js"></script>
|
||||
<script src="/assets/js/page-transitions.js?v=1766709739"></script>
|
||||
<script src="/assets/js/back-button-control.js?v=1766723554"></script>
|
||||
<script src="/assets/js/navigation.js"></script>
|
||||
|
||||
<!-- Cart Drawer -->
|
||||
<div class="cart-overlay"></div>
|
||||
<div class="cart-drawer">
|
||||
<div class="cart-header">
|
||||
<h3>Shopping Cart</h3>
|
||||
<button class="cart-close"><i class="bi bi-x"></i></button>
|
||||
</div>
|
||||
<div class="cart-items">
|
||||
<!-- Cart items rendered via JavaScript -->
|
||||
</div>
|
||||
<div class="cart-footer">
|
||||
<div class="cart-total">
|
||||
<span>Total:</span>
|
||||
<span class="cart-total-amount">$0.00</span>
|
||||
</div>
|
||||
<a href="/checkout" class="btn btn-primary cart-checkout-btn"
|
||||
>Checkout</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Wishlist Drawer -->
|
||||
<div class="wishlist-overlay"></div>
|
||||
<div class="wishlist-drawer">
|
||||
<div class="wishlist-header">
|
||||
<h3><i class="bi bi-heart"></i> My Wishlist</h3>
|
||||
<button class="wishlist-close"><i class="bi bi-x"></i></button>
|
||||
</div>
|
||||
<div class="wishlist-items"></div>
|
||||
<div class="wishlist-footer">
|
||||
<a href="/shop" class="btn btn-outline">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="/assets/js/modern-theme.js?v=20260118c"></script>
|
||||
<script src="/assets/js/customer-auth.js"></script>
|
||||
<script src="/assets/js/accessibility.js"></script>
|
||||
<script>
|
||||
// Load shipping page data from API
|
||||
async function loadShippingPageData() {
|
||||
try {
|
||||
const response = await fetch("/api/pages/shipping-info");
|
||||
if (!response.ok) {
|
||||
console.log("Shipping page not found in database, using defaults");
|
||||
renderDefaultShippingContent();
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const pageData = data.pagedata || {};
|
||||
|
||||
// Update header if data exists
|
||||
if (pageData.header) {
|
||||
if (pageData.header.title) {
|
||||
document.getElementById("shippingHeaderTitle").innerHTML =
|
||||
'<i class="bi bi-truck"></i> ' + pageData.header.title;
|
||||
}
|
||||
if (pageData.header.subtitle) {
|
||||
document.getElementById("shippingHeaderSubtitle").textContent =
|
||||
pageData.header.subtitle;
|
||||
}
|
||||
}
|
||||
|
||||
// Render sections
|
||||
const container = document.getElementById("shippingContainer");
|
||||
if (pageData.sections && pageData.sections.length > 0) {
|
||||
container.innerHTML = pageData.sections
|
||||
.map((section) => {
|
||||
let sectionHtml = "";
|
||||
if (section.title) {
|
||||
sectionHtml += `<h2>${escapeHtml(section.title)}</h2>`;
|
||||
}
|
||||
if (section.content) {
|
||||
// Split content by newlines and render as paragraphs
|
||||
const paragraphs = section.content
|
||||
.split("\n")
|
||||
.filter((p) => p.trim());
|
||||
paragraphs.forEach((p) => {
|
||||
sectionHtml += `<p>${escapeHtml(p)}</p>`;
|
||||
});
|
||||
}
|
||||
if (section.listItems && section.listItems.length > 0) {
|
||||
sectionHtml +=
|
||||
"<ul>" +
|
||||
section.listItems
|
||||
.map((item) => `<li>${escapeHtml(item)}</li>`)
|
||||
.join("") +
|
||||
"</ul>";
|
||||
}
|
||||
return sectionHtml;
|
||||
})
|
||||
.join("");
|
||||
|
||||
// Add contact box at the end
|
||||
container.innerHTML += `
|
||||
<div class="highlight-box">
|
||||
<h3 style="margin-top: 0">Shipping Questions?</h3>
|
||||
<p style="margin-bottom: var(--spacing-md)">
|
||||
Our team is here to help with any shipping inquiries.
|
||||
</p>
|
||||
<div style="display: flex; gap: var(--spacing-md); flex-wrap: wrap">
|
||||
<a href="/contact" class="btn btn-primary">Contact Us</a>
|
||||
<a href="mailto:support@skyartshop.com" class="btn btn-outline">Email Support</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
renderDefaultShippingContent();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading shipping page:", error);
|
||||
renderDefaultShippingContent();
|
||||
}
|
||||
}
|
||||
|
||||
function renderDefaultShippingContent() {
|
||||
const container = document.getElementById("shippingContainer");
|
||||
container.innerHTML = `
|
||||
<div class="highlight-box">
|
||||
<p style="margin: 0; font-weight: 600; font-size: 1.1rem">
|
||||
<i class="bi bi-gift"></i> Free standard shipping on orders over $50!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>Domestic Shipping Options</h2>
|
||||
<div class="shipping-options">
|
||||
<div class="shipping-card">
|
||||
<div class="shipping-icon"><i class="bi bi-truck"></i></div>
|
||||
<h3>Standard Shipping</h3>
|
||||
<p class="price">$5.99</p>
|
||||
<p style="font-size: 0.9rem; color: var(--text-secondary)">5-7 business days</p>
|
||||
<p style="font-size: 0.85rem; color: var(--text-light); margin-top: var(--spacing-sm);">FREE on orders $50+</p>
|
||||
</div>
|
||||
<div class="shipping-card">
|
||||
<div class="shipping-icon"><i class="bi bi-lightning-charge"></i></div>
|
||||
<h3>Express Shipping</h3>
|
||||
<p class="price">$12.99</p>
|
||||
<p style="font-size: 0.9rem; color: var(--text-secondary)">2-3 business days</p>
|
||||
<p style="font-size: 0.85rem; color: var(--text-light); margin-top: var(--spacing-sm);">Guaranteed delivery</p>
|
||||
</div>
|
||||
<div class="shipping-card">
|
||||
<div class="shipping-icon"><i class="bi bi-airplane"></i></div>
|
||||
<h3>Overnight Shipping</h3>
|
||||
<p class="price">$24.99</p>
|
||||
<p style="font-size: 0.9rem; color: var(--text-secondary)">Next business day</p>
|
||||
<p style="font-size: 0.85rem; color: var(--text-light); margin-top: var(--spacing-sm);">Order before 2 PM EST</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Processing Time</h2>
|
||||
<p>All orders are processed within 1-2 business days (excluding weekends and holidays). You will receive a shipping confirmation email with tracking information once your order ships.</p>
|
||||
<ul>
|
||||
<li><strong>In-Stock Items:</strong> Ship within 24 hours</li>
|
||||
<li><strong>Custom Orders:</strong> 3-5 business days processing time</li>
|
||||
<li><strong>Pre-Orders:</strong> Ship on or before the release date</li>
|
||||
</ul>
|
||||
|
||||
<h2>International Shipping</h2>
|
||||
<p>We ship to over 50 countries worldwide! International shipping rates and delivery times vary by destination.</p>
|
||||
|
||||
<div class="highlight-box">
|
||||
<h3 style="margin-top: 0">Shipping Questions?</h3>
|
||||
<p style="margin-bottom: var(--spacing-md)">Our team is here to help with any shipping inquiries.</p>
|
||||
<div style="display: flex; gap: var(--spacing-md); flex-wrap: wrap">
|
||||
<a href="/contact" class="btn btn-primary">Contact Us</a>
|
||||
<a href="mailto:support@skyartshop.com" class="btn btn-outline">Email Support</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Load on page ready
|
||||
document.addEventListener("DOMContentLoaded", loadShippingPageData);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
741
website/public/signin.html
Normal file
741
website/public/signin.html
Normal file
@@ -0,0 +1,741 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Sign In | Sky Art Shop</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
/>
|
||||
<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=Poppins:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--color-bg-main: #ffebeb;
|
||||
--color-bg-secondary: #ffd0d0;
|
||||
--color-bg-promotion: #f6ccde;
|
||||
--color-accent: #fcb1d8;
|
||||
--color-text-main: #202023;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Poppins", -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
#ffebeb 0%,
|
||||
#ffd0d0 50%,
|
||||
#f6ccde 100%
|
||||
);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Decorative elements */
|
||||
.bg-decoration {
|
||||
position: fixed;
|
||||
border-radius: 50%;
|
||||
filter: blur(60px);
|
||||
opacity: 0.5;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.bg-decoration.one {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background: #fcb1d8;
|
||||
top: -100px;
|
||||
left: -100px;
|
||||
}
|
||||
|
||||
.bg-decoration.two {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background: #f6ccde;
|
||||
bottom: -50px;
|
||||
right: -50px;
|
||||
}
|
||||
|
||||
.bg-decoration.three {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: #ffd0d0;
|
||||
top: 50%;
|
||||
right: 20%;
|
||||
}
|
||||
|
||||
.auth-container {
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Logo Section */
|
||||
.logo-section {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.logo-wrapper {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin: 0 auto 20px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 10px 40px rgba(252, 177, 216, 0.4),
|
||||
0 0 0 8px rgba(255, 255, 255, 0.5);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logo-wrapper img {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.logo-section h1 {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-main);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.logo-section p {
|
||||
color: #666;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* Form Card */
|
||||
.form-card {
|
||||
background: white;
|
||||
border-radius: 24px;
|
||||
padding: 40px 36px;
|
||||
box-shadow: 0 20px 60px rgba(252, 177, 216, 0.25),
|
||||
0 8px 20px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* Form Fields */
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-main);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.input-field i.field-icon {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #999;
|
||||
font-size: 1.1rem;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.input-field input {
|
||||
width: 100%;
|
||||
padding: 15px 48px 15px 48px;
|
||||
border: 2px solid #f0f0f0;
|
||||
border-radius: 14px;
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
transition: all 0.2s ease;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.input-field input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
background: white;
|
||||
box-shadow: 0 0 0 4px rgba(252, 177, 216, 0.15);
|
||||
}
|
||||
|
||||
.input-field input:focus + i.field-icon,
|
||||
.input-field input:focus ~ i.field-icon {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.input-field .toggle-password {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.input-field .toggle-password:hover {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* Options Row */
|
||||
.options-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.remember-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.remember-check input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
accent-color: var(--color-accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.remember-check span {
|
||||
font-size: 0.9rem;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.forgot-link {
|
||||
font-size: 0.9rem;
|
||||
color: #e85a9c;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.forgot-link:hover {
|
||||
color: #d14485;
|
||||
}
|
||||
|
||||
/* Submit Button */
|
||||
.btn-signin {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
background: linear-gradient(135deg, #fcb1d8 0%, #f6ccde 100%);
|
||||
color: var(--color-text-main);
|
||||
border: none;
|
||||
border-radius: 14px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-signin::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.4),
|
||||
transparent
|
||||
);
|
||||
transition: left 0.5s;
|
||||
}
|
||||
|
||||
.btn-signin:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.btn-signin:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 12px 28px rgba(252, 177, 216, 0.5);
|
||||
}
|
||||
|
||||
.btn-signin:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* Spinner */
|
||||
.spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(32, 32, 35, 0.2);
|
||||
border-top-color: var(--color-text-main);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Divider */
|
||||
.divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 28px 0;
|
||||
color: #999;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.divider::before,
|
||||
.divider::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, #e0e0e0, transparent);
|
||||
}
|
||||
|
||||
.divider span {
|
||||
padding: 0 16px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Social Buttons */
|
||||
.social-btns {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.social-btn {
|
||||
padding: 14px 12px;
|
||||
border: 2px solid #f0f0f0;
|
||||
border-radius: 12px;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.social-btn:hover {
|
||||
border-color: var(--color-accent);
|
||||
background: #fff9fc;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.social-btn.google {
|
||||
color: #ea4335;
|
||||
}
|
||||
.social-btn.facebook {
|
||||
color: #1877f2;
|
||||
}
|
||||
.social-btn.apple {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
/* Footer Link */
|
||||
.form-footer {
|
||||
text-align: center;
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
.form-footer p {
|
||||
color: #666;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.form-footer a {
|
||||
color: #e85a9c;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
margin-left: 4px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.form-footer a:hover {
|
||||
color: #d14485;
|
||||
}
|
||||
|
||||
/* Toast Notification */
|
||||
.toast {
|
||||
position: fixed;
|
||||
top: 24px;
|
||||
right: 24px;
|
||||
padding: 16px 24px;
|
||||
border-radius: 12px;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
font-size: 0.95rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
transform: translateX(120%);
|
||||
transition: transform 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
z-index: 1000;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.toast.show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.toast.success {
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
}
|
||||
|
||||
.toast.error {
|
||||
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||||
}
|
||||
|
||||
.toast.info {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||
}
|
||||
|
||||
/* Error State */
|
||||
.input-field.error input {
|
||||
border-color: #ef4444;
|
||||
background: #fef2f2;
|
||||
}
|
||||
|
||||
.input-field.error i.field-icon {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ef4444;
|
||||
font-size: 0.8rem;
|
||||
margin-top: 6px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.input-field.error + .error-message {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Back to home link */
|
||||
.back-home {
|
||||
text-align: center;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.back-home a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.back-home a:hover {
|
||||
color: var(--color-text-main);
|
||||
background: rgba(252, 177, 216, 0.2);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Background decorations -->
|
||||
<div class="bg-decoration one"></div>
|
||||
<div class="bg-decoration two"></div>
|
||||
<div class="bg-decoration three"></div>
|
||||
|
||||
<!-- Toast Notification -->
|
||||
<div class="toast" id="toast">
|
||||
<i class="bi bi-check-circle"></i>
|
||||
<span id="toastMessage">Message</span>
|
||||
</div>
|
||||
|
||||
<div class="auth-container">
|
||||
<!-- Logo Section -->
|
||||
<div class="logo-section">
|
||||
<div class="logo-wrapper">
|
||||
<img
|
||||
src="/uploads/cat-logo-only-1766962993568-201212396.png"
|
||||
alt="Sky Art Shop"
|
||||
/>
|
||||
</div>
|
||||
<h1>Welcome Back!</h1>
|
||||
<p>Sign in to continue your creative journey</p>
|
||||
</div>
|
||||
|
||||
<!-- Form Card -->
|
||||
<div class="form-card">
|
||||
<form id="signinForm" novalidate>
|
||||
<div class="form-group">
|
||||
<label for="email">Email Address</label>
|
||||
<div class="input-field">
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="you@example.com"
|
||||
required
|
||||
/>
|
||||
<i class="bi bi-envelope field-icon"></i>
|
||||
</div>
|
||||
<div class="error-message">Please enter a valid email address</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<div class="input-field">
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Enter your password"
|
||||
required
|
||||
/>
|
||||
<i class="bi bi-lock field-icon"></i>
|
||||
<button
|
||||
type="button"
|
||||
class="toggle-password"
|
||||
onclick="togglePassword()"
|
||||
>
|
||||
<i class="bi bi-eye" id="toggleIcon"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="error-message">Password is required</div>
|
||||
</div>
|
||||
|
||||
<div class="options-row">
|
||||
<label class="remember-check">
|
||||
<input type="checkbox" name="remember" id="remember" />
|
||||
<span>Remember me</span>
|
||||
</label>
|
||||
<a
|
||||
href="#"
|
||||
class="forgot-link"
|
||||
onclick="showToast('Password reset coming soon!', 'info'); return false;"
|
||||
>Forgot password?</a
|
||||
>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-signin" id="submitBtn">
|
||||
<i class="bi bi-box-arrow-in-right"></i>
|
||||
<span id="btnText">Sign In</span>
|
||||
<div class="spinner" id="spinner"></div>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="divider">
|
||||
<span>or continue with</span>
|
||||
</div>
|
||||
|
||||
<div class="social-btns">
|
||||
<button
|
||||
type="button"
|
||||
class="social-btn google"
|
||||
onclick="showToast('Google login coming soon!', 'info')"
|
||||
>
|
||||
<i class="bi bi-google"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="social-btn facebook"
|
||||
onclick="showToast('Facebook login coming soon!', 'info')"
|
||||
>
|
||||
<i class="bi bi-facebook"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="social-btn apple"
|
||||
onclick="showToast('Apple login coming soon!', 'info')"
|
||||
>
|
||||
<i class="bi bi-apple"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="form-footer">
|
||||
<p>Don't have an account?<a href="/signup">Create one</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="back-home">
|
||||
<a href="/"><i class="bi bi-arrow-left"></i> Back to Shop</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Toggle password visibility
|
||||
function togglePassword() {
|
||||
const passwordInput = document.getElementById("password");
|
||||
const toggleIcon = document.getElementById("toggleIcon");
|
||||
|
||||
if (passwordInput.type === "password") {
|
||||
passwordInput.type = "text";
|
||||
toggleIcon.classList.remove("bi-eye");
|
||||
toggleIcon.classList.add("bi-eye-slash");
|
||||
} else {
|
||||
passwordInput.type = "password";
|
||||
toggleIcon.classList.remove("bi-eye-slash");
|
||||
toggleIcon.classList.add("bi-eye");
|
||||
}
|
||||
}
|
||||
|
||||
// Toast notification
|
||||
function showToast(message, type = "success") {
|
||||
const toast = document.getElementById("toast");
|
||||
const toastMessage = document.getElementById("toastMessage");
|
||||
const icon = toast.querySelector("i");
|
||||
|
||||
toastMessage.textContent = message;
|
||||
toast.className = "toast " + type;
|
||||
|
||||
// Update icon based on type
|
||||
icon.className = "bi";
|
||||
if (type === "success") icon.classList.add("bi-check-circle");
|
||||
else if (type === "error") icon.classList.add("bi-exclamation-circle");
|
||||
else if (type === "info") icon.classList.add("bi-info-circle");
|
||||
|
||||
toast.classList.add("show");
|
||||
|
||||
setTimeout(() => {
|
||||
toast.classList.remove("show");
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
// Form submission
|
||||
document
|
||||
.getElementById("signinForm")
|
||||
.addEventListener("submit", async function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const email = document.getElementById("email").value.trim();
|
||||
const password = document.getElementById("password").value;
|
||||
const remember = document.getElementById("remember").checked;
|
||||
|
||||
// Clear previous errors
|
||||
document
|
||||
.querySelectorAll(".input-field")
|
||||
.forEach((f) => f.classList.remove("error"));
|
||||
|
||||
// Validate
|
||||
let hasError = false;
|
||||
|
||||
if (!email || !email.includes("@")) {
|
||||
document
|
||||
.getElementById("email")
|
||||
.parentElement.classList.add("error");
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
document
|
||||
.getElementById("password")
|
||||
.parentElement.classList.add("error");
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
if (hasError) return;
|
||||
|
||||
// Show loading state
|
||||
const submitBtn = document.getElementById("submitBtn");
|
||||
const btnText = document.getElementById("btnText");
|
||||
const spinner = document.getElementById("spinner");
|
||||
|
||||
submitBtn.disabled = true;
|
||||
btnText.style.display = "none";
|
||||
submitBtn.querySelector("i").style.display = "none";
|
||||
spinner.style.display = "block";
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/customers/login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email, password }),
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Store customer data
|
||||
localStorage.setItem("customer", JSON.stringify(data.customer));
|
||||
|
||||
if (remember) {
|
||||
localStorage.setItem("rememberCustomer", "true");
|
||||
}
|
||||
|
||||
showToast("Welcome back! Redirecting...", "success");
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.href = "/account";
|
||||
}, 1200);
|
||||
} else {
|
||||
showToast(data.message || "Invalid email or password", "error");
|
||||
|
||||
// Reset button
|
||||
submitBtn.disabled = false;
|
||||
btnText.style.display = "inline";
|
||||
submitBtn.querySelector("i").style.display = "inline";
|
||||
spinner.style.display = "none";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Login error:", error);
|
||||
showToast("Connection error. Please try again.", "error");
|
||||
|
||||
// Reset button
|
||||
submitBtn.disabled = false;
|
||||
btnText.style.display = "inline";
|
||||
submitBtn.querySelector("i").style.display = "inline";
|
||||
spinner.style.display = "none";
|
||||
}
|
||||
});
|
||||
|
||||
// Check if already logged in
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const customer = localStorage.getItem("customer");
|
||||
if (customer) {
|
||||
try {
|
||||
const parsed = JSON.parse(customer);
|
||||
if (parsed && parsed.email) {
|
||||
// Already logged in, redirect to account
|
||||
window.location.href = "/account";
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1243
website/public/signup.html
Normal file
1243
website/public/signup.html
Normal file
File diff suppressed because it is too large
Load Diff
131
website/public/sticky-test-inline.html
Normal file
131
website/public/sticky-test-inline.html
Normal file
@@ -0,0 +1,131 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sticky Test - Inline CSS</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
/* STICKY NAVBAR - This should work */
|
||||
.sticky-navbar-wrapper {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
background: #FFD0D0;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.navbar-content {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #202023;
|
||||
}
|
||||
|
||||
.navbar-status {
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 40px 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.section h2 {
|
||||
color: #202023;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.scroll-indicator {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background: #FFD0D0;
|
||||
padding: 15px 25px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- THIS IS THE STICKY NAVBAR -->
|
||||
<div class="sticky-navbar-wrapper">
|
||||
<div class="navbar-content">
|
||||
<div class="navbar-title">🔍 STICKY NAVBAR TEST</div>
|
||||
<div class="navbar-status">I should stay at top when scrolling ↓</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CONTENT TO SCROLL -->
|
||||
<div class="content">
|
||||
<div class="section">
|
||||
<h2>Section 1 - Start Scrolling</h2>
|
||||
<p style="font-size: 18px; line-height: 1.8;">
|
||||
If the pink navbar above stays visible while you scroll, then position: sticky is working correctly.
|
||||
<br><br>
|
||||
<strong>What to look for:</strong>
|
||||
<br>• The pink navbar should remain at the top of the window
|
||||
<br>• It should NOT scroll away with the content
|
||||
<br>• The green badge should always be visible
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Section 2 - Keep Scrolling</h2>
|
||||
<p style="font-size: 18px;">Continue scrolling down to test the sticky behavior...</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Section 3 - More Scrolling</h2>
|
||||
<p style="font-size: 18px;">The navbar should still be at the top...</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Section 4 - Almost There</h2>
|
||||
<p style="font-size: 18px;">Keep going...</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Section 5 - Final Test</h2>
|
||||
<p style="font-size: 18px;">
|
||||
If you can still see the pink navbar at the top, it's working!
|
||||
<br><br>
|
||||
If it scrolled away, there's a browser or CSS issue preventing sticky positioning.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="scroll-indicator">
|
||||
Scroll to test ↑↓
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
29
website/public/test-sticky-navbar.html
Normal file
29
website/public/test-sticky-navbar.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Sticky Navbar Test</title>
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css?v=1768450104" />
|
||||
<link rel="stylesheet" href="/assets/css/page-overrides.css?v=1768450104" />
|
||||
<style>
|
||||
body { margin: 0; padding: 0; }
|
||||
.test-content { height: 2000px; padding: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sticky-banner-wrapper">
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper" style="padding: 20px;">
|
||||
<h2 style="margin: 0; color: #202023;">TEST: Navbar Should Stick When Scrolling</h2>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="test-content">
|
||||
<h1>Scroll down to test sticky navbar</h1>
|
||||
<p>If the navbar stays at the top, CSS is working.</p>
|
||||
<p>If the navbar scrolls away, there's still an issue.</p>
|
||||
<p style="margin-top: 500px;">Keep scrolling...</p>
|
||||
<p style="margin-top: 500px;">Almost there...</p>
|
||||
<p style="margin-top: 500px;">End of page</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
65
website/public/test-sticky.html
Normal file
65
website/public/test-sticky.html
Normal file
@@ -0,0 +1,65 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sticky Test</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #ffebeb;
|
||||
min-height: 200vh; /* Make it scrollable */
|
||||
}
|
||||
|
||||
.sticky-banner-wrapper {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
background: #ffd0d0;
|
||||
}
|
||||
|
||||
.test-navbar {
|
||||
padding: 20px;
|
||||
background: #ffd0d0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 50px;
|
||||
font-size: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sticky-banner-wrapper">
|
||||
<div class="test-navbar">
|
||||
<h2>TEST NAVBAR - Should stay at top when scrolling</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<h1>Scroll down to test sticky behavior</h1>
|
||||
<p>Content line 1</p>
|
||||
<p>Content line 2</p>
|
||||
<p>Content line 3</p>
|
||||
<p>Content line 4</p>
|
||||
<p>Content line 5</p>
|
||||
<p>Content line 6</p>
|
||||
<p>Content line 7</p>
|
||||
<p>Content line 8</p>
|
||||
<p>Content line 9</p>
|
||||
<p>Content line 10</p>
|
||||
<p>Content line 11</p>
|
||||
<p>Content line 12</p>
|
||||
<p>Content line 13</p>
|
||||
<p>Content line 14</p>
|
||||
<p>Content line 15</p>
|
||||
<p>Content line 16</p>
|
||||
<p>Content line 17</p>
|
||||
<p>Content line 18</p>
|
||||
<p>Content line 19</p>
|
||||
<p>Content line 20</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user