reset repo

This commit is contained in:
George Birikorang 2025-09-19 21:51:10 -07:00
parent 7f70170fe1
commit 9cdef853c4
631 changed files with 4941 additions and 16827 deletions

97
.gitignore vendored
View file

@ -4,8 +4,49 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
logs/
*.log
sync-log.json
# Temporary files
temp-main/
.tmp/
.temp/
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# Build outputs
dist/
build/
# Cache
.cache/
.parcel-cache/
# Runtime data
pids
pids/
*.pid
*.seed
*.pid.lock
@ -14,19 +55,7 @@ pids
coverage/
# nyc test coverage
.nyc_output
# Grunt intermediate storage
.grunt
# Bower dependency directory
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons
build/Release
.nyc_output/
# Dependency directories
jspm_packages/
@ -46,26 +75,30 @@ jspm_packages/
# dotenv environment variables file
.env
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# next.js build output
.next
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Nuxt.js build output
.nuxt
# Logs
logs
*.log
# Gatsby files
.cache/
public
# Temporary files
# Storybook build outputs
.out
.storybook-out
# Temporary folders
tmp/
temp/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View file

@ -1,150 +0,0 @@
# Deploy Setup Guide
This guide explains how to configure the admin dashboard to deploy to your main KHY website when they are in separate repositories.
## Repository Structure Examples
### Example 1: Sibling Directories
```
Documents/
├── khy_website/ # Main website repository
│ ├── index.html
│ ├── data/
│ └── assets/
└── khy_admin/ # Admin repository
├── admin.html
├── data/
└── assets/
```
**Setup**: Run `npm run setup` and enter `../khy_website`
### Example 2: Different Parent Directories
```
Documents/
├── projects/
│ └── khy_website/ # Main website repository
└── admin_tools/
└── khy_admin/ # Admin repository
```
**Setup**: Run `npm run setup` and enter `../../projects/khy_website`
### Example 3: Absolute Paths
```
/Users/george/Documents/khy_website/ # Main website
/Users/george/Documents/admin_tools/ # Admin repository
```
**Setup**: Run `npm run setup` and enter `/Users/george/Documents/khy_website`
## Setup Process
1. **Run the setup script**:
```bash
npm run setup
```
2. **Enter the path to your main KHY website**:
- Use relative paths like `../khy_website` or `../../projects/khy_website`
- Use absolute paths like `/full/path/to/khy_website`
3. **Verify the configuration**:
- The script will check if the path exists
- It will verify required files are present
- It will update `deploy-config.js` with your settings
## Manual Configuration
If you prefer to configure manually, edit `deploy-config.js`:
```javascript
module.exports = {
targets: {
main: "../khy_website", // Change this to your path
},
defaultTarget: "main",
// ... rest of config
};
```
## Testing the Setup
After setup, test the configuration:
```bash
# Test with the configured path
npm run deploy
# Test with a specific path
node deploy.js /path/to/your/khy_website
```
## Troubleshooting
### "Target directory does not exist"
- Check the path you entered during setup
- Use absolute paths if relative paths don't work
- Make sure the main website directory exists
### "Missing required files"
- Ensure your main website has `index.html` and `data/products.json`
- The admin will create these files if they don't exist
### "Permission denied"
- Make sure you have write permissions to the main website directory
- On macOS/Linux, you might need to adjust file permissions
## Advanced Usage
### Multiple Targets
You can configure multiple deployment targets:
```javascript
targets: {
staging: "../khy_website_staging",
production: "../khy_website",
backup: "/backup/khy_website"
}
```
Then deploy to specific targets:
```bash
node deploy.js staging
node deploy.js production
```
### Custom File Mappings
Modify `filesToCopy` in `deploy-config.js` to copy different files:
```javascript
filesToCopy: [
{
source: "data/products.json",
target: "data/products.json",
description: "Product catalog data",
},
{
source: "assets/images",
target: "assets/images",
description: "Product images",
isDirectory: true,
},
{
source: "config/settings.json",
target: "config/admin-settings.json",
description: "Admin settings",
},
];
```

279
README.md
View file

@ -1,146 +1,217 @@
# KHY Admin Dashboard
A **preview/staging environment** for managing KHY Furniture's product catalog with safe testing and one-click deployment to production.
A comprehensive product management dashboard for the KHY website, allowing you to manage products, categories, and images with live preview capabilities.
## Features
- **Add Products**: Create new products with full details
- **Edit Products**: Modify existing product information
- **Delete Products**: Remove products with confirmation
- **Preview Products**: See how products will look before saving
- **Export JSON**: Download updated products.json file
- **Live Preview**: See exactly how changes will look on the website
- **One-Click Deploy**: Deploy changes to production with a single command
- **Automatic Backups**: Safe deployment with backup system
- **No Technical Knowledge Required**: Simple form-based interface
- **Product Management**: Add, edit, delete, and duplicate products
- **Category Management**: Organize products into categories
- **Image Management**: Upload, organize, and manage product images
- **Live Preview**: Preview how changes will look on the actual website
- **Sync to Main Site**: Deploy changes to your main website repository
- **Responsive Design**: Works on desktop, tablet, and mobile devices
## Quick Start
1. **Setup deploy paths** (first time only):
### 1. Setup
```bash
npm run setup
# Follow the prompts to configure where your main KHY website is located
```
```bash
# Navigate to the admin dashboard directory
cd admin-dashboard
2. **Open the dashboard**:
# Install dependencies (optional - for image processing)
npm install
```bash
# Option 1: Double-click admin.html in your file explorer
# Option 2: Run a local server
npm start
# Then open http://localhost:8080/admin.html
```
# Start the development server
npm start
```
3. **Preview the website** (optional):
### 2. Access the Dashboard
```bash
# Option A: Auto-start server and open all preview pages
npm run preview
Open your browser and go to: `http://localhost:3000`
# Option B: Start server manually
npm start
# Then open http://localhost:8080/index.html (homepage)
# Open http://localhost:8080/product-catalog.html (catalog)
# Open http://localhost:8080/product-detail.html (product details)
```
### 3. Using the Dashboard
4. **Manage products**:
#### Products Section
- Add new products using the form
- Edit existing products by clicking "Edit"
- Delete products by clicking "Delete"
- Preview changes before saving
- **Add Product**: Click "Add Product" to create a new product
- **Edit Product**: Click the "Edit" button on any product card
- **Duplicate Product**: Click "Duplicate" to create a copy of an existing product
- **Delete Product**: Click "Delete" to remove a product (with confirmation)
- **Search**: Use the search bar to find specific products
5. **Deploy to production**:
#### Categories Section
```bash
# Option A: Automated deploy (recommended)
npm run deploy
# This copies admin/data/products.json → main website data/products.json
# And copies admin/assets/images/ → main website assets/images/
- **Add Category**: Create new product categories
- **Edit Category**: Modify existing categories
- **View Products**: See all products in a specific category
- **Delete Category**: Remove categories (only if no products are using them)
# Option B: Manual deploy
# Click "Download products.json" and replace in main website
```
#### Images Section
## Complete Workflow
- **Upload Images**: Drag and drop or click to upload new images
- **View Images**: See all uploaded images with metadata
- **Rename Images**: Change image names
- **Delete Images**: Remove unused images
1. **Make Changes** → Use admin dashboard to add/edit/delete products
2. **Preview Changes** → Open `index.html`, `product-catalog.html`, `product-detail.html` to see how changes look
3. **Confirm Changes** → Review everything thoroughly
4. **Deploy to Production** → Run `npm run deploy` to copy updated files to the main KHY website
#### Preview Section
> **Detailed Workflow**: See [WORKFLOW.md](WORKFLOW.md) for complete instructions
- **Product Catalog**: See how your product catalog looks to customers
- **Product Detail**: Preview individual product pages
- **Interactive**: Click on products in catalog to view details
### 4. Syncing to Main Site
When you're ready to deploy your changes:
1. Click the "Sync to Main Site" button
2. Watch the sync progress in the modal
3. Your changes will be committed and pushed to the main repository
## File Structure
```
admin/
├── admin.html # Main admin dashboard
├── index.html # Homepage (reference)
├── product-catalog.html # Product catalog page (reference)
├── product-detail.html # Product detail page (reference)
admin-dashboard/
├── src/
│ ├── index.html # Main dashboard interface
│ ├── css/
│ │ └── admin.css # Dashboard styling
│ └── js/
│ ├── admin.js # Main dashboard controller
│ ├── productManager.js # Product management logic
│ ├── categoryManager.js # Category management logic
│ └── imageManager.js # Image management logic
├── data/
│ └── products.json # Product data file
├── assets/ # Images and resources
├── scripts/ # JavaScript files
├── styles/ # CSS files
├── src/ # Source files
├── tailwind.config.js # Tailwind configuration
├── deploy.js # Deploy script
├── start-server.js # Auto-start server script
├── package.json # Project configuration
├── README.md # This file
└── WORKFLOW.md # Detailed workflow guide
│ └── products.json # Product and category data
├── assets/
│ └── images/
│ └── products/ # Product images
├── preview/
│ ├── catalog.html # Product catalog preview
│ └── detail.html # Product detail preview
├── scripts/
│ └── sync-to-main.js # Sync script for deployment
├── package.json # Node.js dependencies
└── README.md # This file
```
## Usage Instructions
## Configuration
### Adding a New Product
### Main Repository URL
1. Fill out the product form with:
- Product name and description
- Category selection
- Price and model number
- Available sizes and colors
- Stock status and rating
2. Click "Preview Product" to see how it looks
3. Click "Add Product" to save
Before using the sync feature, update the repository URL in `scripts/sync-to-main.js`:
### Editing a Product
```javascript
this.mainRepoUrl = "https://github.com/your-username/khy-website.git";
```
1. Click "Edit" on any product in the list
2. The form will auto-fill with existing data
3. Make your changes
4. Click "Preview Product" to see changes
5. Click "Update Product" to save or "Cancel Edit" to abort
### Data Structure
### Deleting a Product
The dashboard expects your `products.json` to have this structure:
1. Click "Delete" on any product
2. Confirm the deletion in the popup
3. Product is removed immediately
```json
{
"products": [
{
"id": 1,
"name": "Product Name",
"description": "Short description",
"descriptionLong": ["Paragraph 1", "Paragraph 2"],
"category": "category-id",
"image": "assets/images/products/image.jpg",
"images": [
"assets/images/products/image1.jpg",
"assets/images/products/image2.jpg"
],
"galleryPairs": [
"assets/images/products/image1.jpg",
"assets/images/products/image2.jpg"
],
"additionalInformation": {
"Material": "Product material",
"Design": "Design description",
"Use Cases": "Use case description"
},
"warranty": "5 years"
}
],
"categories": [
{
"id": "category-id",
"name": "Category Name",
"description": "Category description"
}
]
}
```
### Exporting Changes
## Development
1. After making all your changes
2. Click "Download products.json"
3. Replace the existing `data/products.json` file in your main website
4. Your website will show the updated products
### Adding New Features
## Technical Notes
1. **New Product Fields**: Add form fields in `src/index.html` and handle them in `productManager.js`
2. **New Category Fields**: Extend the category form and logic in `categoryManager.js`
3. **New Image Features**: Add functionality in `imageManager.js`
- **No server required**: Works entirely in the browser
- **No database needed**: Uses JSON file storage
- **Cross-platform**: Works on Windows, Mac, Linux
- **Offline capable**: Works without internet connection
- **Simple deployment**: Just copy files to any web server
### Styling
The dashboard uses a custom CSS framework with:
- **Colors**: Gradient-based color scheme
- **Typography**: Montserrat + Playfair Display fonts
- **Components**: Card-based layout with hover effects
- **Responsive**: Mobile-first responsive design
### API Integration
Currently, the dashboard works with local JSON files. To integrate with a real API:
1. Replace `fetch('data/products.json')` calls with API endpoints
2. Update the `saveData()` methods to POST to your API
3. Handle authentication and error states
## Deployment
### Option 1: Separate Repository (Recommended)
1. Move this admin dashboard to its own repository
2. Update the sync script with your main repository URL
3. Deploy the admin dashboard to a separate domain/subdomain
4. Use the sync feature to deploy changes to your main site
### Option 2: Subdirectory
1. Keep the admin dashboard in a subdirectory of your main site
2. Add authentication to protect the admin area
3. Use the sync feature to update the main site files
## Security Considerations
- **Authentication**: Add login functionality before deploying
- **File Uploads**: Validate and sanitize uploaded images
- **Data Validation**: Validate all form inputs
- **Access Control**: Restrict access to authorized users only
## Troubleshooting
### Common Issues
1. **Images not loading**: Check file paths and ensure images exist
2. **Sync fails**: Verify repository URL and Git credentials
3. **Preview not working**: Ensure the preview HTML files are accessible
4. **Data not saving**: Check browser console for JavaScript errors
### Getting Help
- Check the browser console for error messages
- Verify all file paths are correct
- Ensure the data structure matches the expected format
- Test with a small dataset first
## License
This admin dashboard is part of the KHY website project.
## Support
For technical support or questions about the admin dashboard, contact your development team.
## Version History
- **v1.0.0**: Initial release with full CRUD functionality
For support or questions about the admin dashboard, please contact the development team.

View file

@ -1,164 +0,0 @@
# KHY Admin Workflow Guide
## Overview
This admin dashboard provides a **preview/staging environment** where you can make changes, see exactly how they'll look on the website, and then deploy to production with confidence.
## Complete Workflow
### 1. 🎯 **Make Changes**
- Open `admin.html` in your browser
- Add, edit, or delete products using the form interface
- Use "Preview Product" to see how individual products will look
### 2. 👀 **Preview Changes**
#### Option A: Auto-Start Preview (Recommended)
```bash
# One command to start server and open all preview pages
npm run preview
# Server will automatically stop when:
# - All preview tabs are closed
# - Deploy command is run
# - Manual stop: npm run preview:kill
```
#### Option B: Manual Preview
- Click "👀 Preview Website" button in admin dashboard
- Or manually open `index.html`, `product-catalog.html`, `product-detail.html`
- Navigate through the site to ensure everything looks correct
### 3. ✅ **Confirm Changes**
- Review all changes thoroughly
- Test product links and functionality
- Ensure images load correctly
### 4. 🚀 **Deploy to Production**
The deploy process copies updated files from the **admin directory** to the **main KHY website directory**.
#### Option A: Automated Deploy (Recommended)
```bash
# Navigate to admin directory
cd admin
# Copy updated files to main website
npm run deploy
```
**What this does:**
- Copies `admin/data/products.json``../data/products.json` (main website)
- Copies `admin/assets/images/``../assets/images/` (main website)
- Creates backup of current main website files
- Updates the live KHY website immediately
#### Option B: Manual Deploy
1. Click "Download products.json" in the admin dashboard
2. Manually copy the downloaded file to `../data/products.json`
3. Manually copy any new images from `admin/assets/images/` to `../assets/images/`
## File Structure
```
admin/ # Admin/Staging Environment
├── admin.html # Admin dashboard
├── index.html # Homepage preview
├── product-catalog.html # Catalog preview
├── product-detail.html # Product detail preview
├── data/products.json # Staging product data
├── assets/images/ # Staging images
├── deploy.js # Deploy script
└── package.json # NPM scripts
../ # Main Website (Production)
├── index.html # Live homepage
├── product-catalog.html # Live catalog
├── product-detail.html # Live product details
├── data/products.json # Live product data
└── assets/images/ # Live images
```
## Key Benefits
**Safe Testing**: Make changes without affecting the live site
**Visual Preview**: See exactly how changes will look
**Easy Deployment**: One-click deploy to production
**Backup System**: Automatic backups before deployment
**No Technical Knowledge**: Simple interface for non-technical users
## Deployment Process
### What Happens During Deploy:
1. **Stop Preview Server**: Automatically stops the preview server (if running)
2. **Validation**: Checks that main KHY website directory exists and has required files
3. **Backup**: Creates timestamped backup of current main website data
4. **Copy Files**:
- Copies `admin/data/products.json``../data/products.json` (main website)
- Copies `admin/assets/images/``../assets/images/` (main website)
5. **Verification**: Confirms all files were copied successfully
6. **Completion**: Main KHY website is updated immediately with new product data
### Backup System:
- All deployments create automatic backups in `admin/backups/`
- Backups are timestamped for easy identification
- Can restore from backup if needed
## Troubleshooting
### Deploy Fails?
- Check that you're in the admin directory
- Verify the main website directory exists
- Ensure you have write permissions
### Changes Not Showing?
- Clear browser cache (Ctrl+F5 or Cmd+Shift+R)
- Check that the correct `products.json` file was updated
- Verify image paths are correct
### Need to Restore?
- Find the backup in `admin/backups/`
- Copy the backup `products.json` to the main website
- Or run the deploy script again
## Best Practices
1. **Always Preview**: Use the preview pages before deploying
2. **Test Everything**: Check all product links and images
3. **Deploy Regularly**: Don't let changes pile up
4. **Keep Backups**: The system creates them automatically
5. **Document Changes**: Keep notes of what you changed and why
## Quick Commands
```bash
# Start preview server
npm start
# Deploy to production
npm run deploy
# View help
npm run preview
```
## Support
If you encounter any issues:
1. Check the browser console for errors
2. Verify file permissions
3. Ensure all required files exist
4. Contact your development team for assistance

View file

@ -1,104 +0,0 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
logs/
*.log
sync-log.json
# Temporary files
temp-main/
.tmp/
.temp/
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# Build outputs
dist/
build/
# Cache
.cache/
.parcel-cache/
# Runtime data
pids/
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
# nyc test coverage
.nyc_output/
# Dependency directories
jspm_packages/
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
# Nuxt.js build output
.nuxt
# Gatsby files
.cache/
public
# Storybook build outputs
.out
.storybook-out
# Temporary folders
tmp/
temp/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View file

@ -1,217 +0,0 @@
# KHY Admin Dashboard
A comprehensive product management dashboard for the KHY website, allowing you to manage products, categories, and images with live preview capabilities.
## Features
- **Product Management**: Add, edit, delete, and duplicate products
- **Category Management**: Organize products into categories
- **Image Management**: Upload, organize, and manage product images
- **Live Preview**: Preview how changes will look on the actual website
- **Sync to Main Site**: Deploy changes to your main website repository
- **Responsive Design**: Works on desktop, tablet, and mobile devices
## Quick Start
### 1. Setup
```bash
# Navigate to the admin dashboard directory
cd admin-dashboard
# Install dependencies (optional - for image processing)
npm install
# Start the development server
npm start
```
### 2. Access the Dashboard
Open your browser and go to: `http://localhost:3000`
### 3. Using the Dashboard
#### Products Section
- **Add Product**: Click "Add Product" to create a new product
- **Edit Product**: Click the "Edit" button on any product card
- **Duplicate Product**: Click "Duplicate" to create a copy of an existing product
- **Delete Product**: Click "Delete" to remove a product (with confirmation)
- **Search**: Use the search bar to find specific products
#### Categories Section
- **Add Category**: Create new product categories
- **Edit Category**: Modify existing categories
- **View Products**: See all products in a specific category
- **Delete Category**: Remove categories (only if no products are using them)
#### Images Section
- **Upload Images**: Drag and drop or click to upload new images
- **View Images**: See all uploaded images with metadata
- **Rename Images**: Change image names
- **Delete Images**: Remove unused images
#### Preview Section
- **Product Catalog**: See how your product catalog looks to customers
- **Product Detail**: Preview individual product pages
- **Interactive**: Click on products in catalog to view details
### 4. Syncing to Main Site
When you're ready to deploy your changes:
1. Click the "Sync to Main Site" button
2. Watch the sync progress in the modal
3. Your changes will be committed and pushed to the main repository
## File Structure
```
admin-dashboard/
├── src/
│ ├── index.html # Main dashboard interface
│ ├── css/
│ │ └── admin.css # Dashboard styling
│ └── js/
│ ├── admin.js # Main dashboard controller
│ ├── productManager.js # Product management logic
│ ├── categoryManager.js # Category management logic
│ └── imageManager.js # Image management logic
├── data/
│ └── products.json # Product and category data
├── assets/
│ └── images/
│ └── products/ # Product images
├── preview/
│ ├── catalog.html # Product catalog preview
│ └── detail.html # Product detail preview
├── scripts/
│ └── sync-to-main.js # Sync script for deployment
├── package.json # Node.js dependencies
└── README.md # This file
```
## Configuration
### Main Repository URL
Before using the sync feature, update the repository URL in `scripts/sync-to-main.js`:
```javascript
this.mainRepoUrl = "https://github.com/your-username/khy-website.git";
```
### Data Structure
The dashboard expects your `products.json` to have this structure:
```json
{
"products": [
{
"id": 1,
"name": "Product Name",
"description": "Short description",
"descriptionLong": ["Paragraph 1", "Paragraph 2"],
"category": "category-id",
"image": "assets/images/products/image.jpg",
"images": [
"assets/images/products/image1.jpg",
"assets/images/products/image2.jpg"
],
"galleryPairs": [
"assets/images/products/image1.jpg",
"assets/images/products/image2.jpg"
],
"additionalInformation": {
"Material": "Product material",
"Design": "Design description",
"Use Cases": "Use case description"
},
"warranty": "5 years"
}
],
"categories": [
{
"id": "category-id",
"name": "Category Name",
"description": "Category description"
}
]
}
```
## Development
### Adding New Features
1. **New Product Fields**: Add form fields in `src/index.html` and handle them in `productManager.js`
2. **New Category Fields**: Extend the category form and logic in `categoryManager.js`
3. **New Image Features**: Add functionality in `imageManager.js`
### Styling
The dashboard uses a custom CSS framework with:
- **Colors**: Gradient-based color scheme
- **Typography**: Montserrat + Playfair Display fonts
- **Components**: Card-based layout with hover effects
- **Responsive**: Mobile-first responsive design
### API Integration
Currently, the dashboard works with local JSON files. To integrate with a real API:
1. Replace `fetch('data/products.json')` calls with API endpoints
2. Update the `saveData()` methods to POST to your API
3. Handle authentication and error states
## Deployment
### Option 1: Separate Repository (Recommended)
1. Move this admin dashboard to its own repository
2. Update the sync script with your main repository URL
3. Deploy the admin dashboard to a separate domain/subdomain
4. Use the sync feature to deploy changes to your main site
### Option 2: Subdirectory
1. Keep the admin dashboard in a subdirectory of your main site
2. Add authentication to protect the admin area
3. Use the sync feature to update the main site files
## Security Considerations
- **Authentication**: Add login functionality before deploying
- **File Uploads**: Validate and sanitize uploaded images
- **Data Validation**: Validate all form inputs
- **Access Control**: Restrict access to authorized users only
## Troubleshooting
### Common Issues
1. **Images not loading**: Check file paths and ensure images exist
2. **Sync fails**: Verify repository URL and Git credentials
3. **Preview not working**: Ensure the preview HTML files are accessible
4. **Data not saving**: Check browser console for JavaScript errors
### Getting Help
- Check the browser console for error messages
- Verify all file paths are correct
- Ensure the data structure matches the expected format
- Test with a small dataset first
## License
This admin dashboard is part of the KHY website project.
## Support
For support or questions about the admin dashboard, please contact the development team.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 884 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 MiB

File diff suppressed because it is too large Load diff

View file

@ -1,24 +0,0 @@
{
"name": "khy-admin",
"version": "1.0.0",
"description": "KHY Product Management Dashboard",
"main": "src/index.html",
"scripts": {
"start": "python3 -m http.server 3000",
"sync": "node scripts/sync-to-main.js",
"dev": "python3 -m http.server 3000"
},
"dependencies": {
"sharp": "^0.32.0"
},
"devDependencies": {
"nodemon": "^3.0.0"
},
"keywords": [
"admin",
"dashboard",
"product-management"
],
"author": "KHY",
"license": "MIT"
}

View file

@ -1,272 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Product Detail - KHY</title>
<link rel="stylesheet" href="assets/styles/main.css" />
<style>
/* Force responsive behavior */
@media (min-width: 640px) {
.sm\:hidden {
display: none !important;
}
.sm\:flex {
display: flex !important;
}
}
</style>
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&family=Playfair+Display:wght@100;200;300;400;500;600;700&family=Poppins:wght@400;500;600&display=swap"
rel="stylesheet"
/>
</head>
<body class="bg-white font-sans text-gray-800">
<!-- Header -->
<header
class="fixed w-full h-20 sm:h-28 top-0 left-0 bg-white shadow-[0_8px_24px_rgba(0,0,0,0.06)] border-b border-black/10 z-50"
>
<nav class="h-full">
<div
class="max-w-7xl mx-auto h-full flex items-center justify-between px-5"
></div>
</nav>
</header>
<main class="pt-20 sm:pt-28">
<!-- Product Details Section -->
<section class="relative w-full bg-white py-6">
<div class="max-w-7xl mx-auto px-5">
<div class="flex items-center">
<h1
class="font-playfair font-normal text-base text-quick-silver"
id="product-details-title"
>
Product Details
</h1>
</div>
</div>
</section>
<!-- Main Product Section -->
<section class="relative w-full bg-white py-8">
<div class="max-w-7xl mx-auto px-5">
<div class="flex flex-col md:flex-row gap-8">
<!-- Left Section - Product Images -->
<div class="flex flex-col-reverse md:flex-row gap-4">
<!-- Thumbnails -->
<div
class="flex flex-row md:flex-col gap-3 md:gap-4 w-full md:w-32 overflow-x-auto md:overflow-visible"
>
<!-- Thumbnail images will be populated dynamically -->
</div>
<!-- Main Product Image -->
<div
class="w-full h-80 md:w-[500px] md:h-[500px] bg-floral-white rounded-lg overflow-hidden"
>
<!-- Main product image will be populated dynamically -->
</div>
</div>
<!-- Right Section - Product Details -->
<div class="w-full md:w-[500px]">
<!-- Product Title -->
<h1
class="font-playfair font-normal text-4xl text-black mb-8"
id="product-title"
>
<!-- Product title will be populated dynamically -->
</h1>
<!-- Product Description -->
<p
class="font-playfair font-normal text-sm text-black mb-8 max-w-md"
id="product-description"
>
<!-- Product description will be populated dynamically -->
</p>
<!-- Quantity and Action Buttons -->
<div class="flex flex-col md:flex-row gap-4 md:gap-6 mb-3">
<!-- Action Buttons -->
<a
href="contact.html"
class="inline-flex items-center justify-center w-full md:w-[440px] h-[64px] min-h-[64px] bg-white text-black font-playfair font-light text-[20px] leading-none rounded-[15px] border border-quick-silver hover:bg-black hover:text-white hover:shadow-lg transform hover:-translate-y-0.5 transition-all duration-200 box-border whitespace-nowrap mb-4"
>
Request a Quote
</a>
<button
id="compare-products-btn"
class="inline-flex items-center justify-center w-full md:w-[440px] h-[64px] min-h-[64px] bg-white text-black font-playfair font-light text-[20px] leading-none rounded-[15px] border border-quick-silver hover:bg-black hover:text-white hover:shadow-lg transform hover:-translate-y-0.5 transition-all duration-200 box-border whitespace-nowrap"
>
Compare Products
</button>
</div>
<!-- Divider -->
<div class="w-full h-px bg-light-silver mb-3"></div>
<!-- Product Specifications -->
<div class="mb-8">
<div class="space-y-2" id="product-specifications">
<!-- Product specifications will be populated dynamically -->
</div>
</div>
<!-- Product Metadata -->
<div class="space-y-4">
<!-- Product metadata will be populated dynamically -->
</div>
</div>
</div>
</div>
</section>
<!-- Product Detail Tabs Section -->
<section id="product-tabs" class="w-full bg-white">
<div class="border-t border-light-silver"></div>
<div class="max-w-7xl mx-auto px-5 py-10">
<!-- Tabs -->
<div class="flex justify-center gap-12 mb-8">
<button class="font-playfair text-2xl text-black">
Description
</button>
</div>
<!-- Copy -->
<div
class="max-w-5xl mx-auto space-y-6 text-center"
id="product-description-content"
>
<!-- Product description content will be populated dynamically -->
</div>
<!-- Images -->
<div class="relative mt-10">
<div
class="grid grid-cols-1 md:grid-cols-2 gap-8"
id="product-gallery-images"
>
<!-- Product gallery images will be populated dynamically -->
</div>
<!-- Back Arrow -->
<button
id="gallery-back-btn"
class="absolute top-1/2 left-0 transform -translate-y-1/2 bg-white border border-gray-300 rounded-full p-3 shadow-lg hover:shadow-xl transition-all duration-200 opacity-0 pointer-events-none"
style="display: none"
>
<svg
class="w-6 h-6 text-gray-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 19l-7-7 7-7"
></path>
</svg>
</button>
<!-- Forward Arrow -->
<button
id="gallery-next-btn"
class="absolute top-1/2 right-0 transform -translate-y-1/2 bg-white border border-gray-300 rounded-full p-3 shadow-lg hover:shadow-xl transition-all duration-200 opacity-0 pointer-events-none"
style="display: none"
>
<svg
class="w-6 h-6 text-gray-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5l7 7-7 7"
></path>
</svg>
</button>
</div>
</div>
</section>
<!-- full-width divider before related products -->
<div class="w-full border-t border-light-silver"></div>
<!-- Related Products Section -->
<section id="related-products" class="w-full bg-white py-14">
<div class="max-w-7xl mx-auto px-5">
<h2
class="font-playfair text-4xl font-medium text-center text-black mb-10"
>
Related Products
</h2>
<div
id="related-grid"
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8"
>
<!-- Related items will be injected here -->
</div>
<div class="mt-10 flex justify-center">
<button
id="related-show-more"
class="w-[245px] h-12 border border-uc-gold rounded-md bg-white font-playfair font-semibold text-base text-uc-gold hover:bg-uc-gold hover:text-white transition-colors"
>
Show More
</button>
</div>
</div>
</section>
<!-- full-width divider after related products -->
<div class="w-full border-t border-light-silver"></div>
</main>
<!-- Image Enlargement Modal -->
<div
id="image-modal"
class="fixed inset-0 bg-black bg-opacity-90 z-50 hidden flex items-center justify-center p-4 transition-opacity duration-300"
>
<div
class="relative max-w-7xl max-h-full w-full h-full flex items-center justify-center"
>
<!-- Close Button -->
<button
id="modal-close-btn"
class="absolute top-4 right-4 z-10 bg-white bg-opacity-20 hover:bg-opacity-30 text-white rounded-full p-3 transition-all duration-200 backdrop-blur-sm"
aria-label="Close modal"
>
<svg
class="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
></path>
</svg>
</button>
<!-- Enlarged Image -->
<img
id="modal-image"
src=""
alt=""
class="w-[95vw] h-[95vh] object-contain rounded-lg shadow-2xl transform transition-transform duration-300 scale-95"
/>
</div>
</div>
<script src="scripts/main.js?v=3.5"></script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -1,596 +0,0 @@
// Helper function to fix image paths for admin dashboard preview
function fixImagePath(imagePath) {
if (!imagePath) return imagePath;
// If the path starts with "assets/", prepend "../" for admin dashboard preview
if (imagePath.startsWith("assets/")) {
return "../" + imagePath;
}
return imagePath;
}
// Product Management System
class ProductManager {
constructor() {
this.products = [];
this.categories = [];
this.pagination = {};
this.currentPage = 1;
this.itemsPerPage = 16;
this.filteredProducts = [];
this.selectedCategories = new Set();
this.tempProducts = null; // For temporary data from admin dashboard
}
// Load products from JSON file
async loadProducts() {
try {
const response = await fetch("/data/products.json");
const data = await response.json();
this.products = data.products;
this.categories = data.categories;
this.pagination = data.pagination;
this.filteredProducts = [...this.products];
this.renderProducts();
this.updatePagination();
this.updateResultsCount();
this.setupEventListeners();
this.renderCategoryFilters();
// Check for URL parameters and pre-select category
this.handleUrlParameters();
} catch (error) {
console.error("Error loading products:", error);
}
}
// Handle URL parameters for pre-selecting category filters
handleUrlParameters() {
const urlParams = new URLSearchParams(window.location.search);
const category = urlParams.get("category");
if (category) {
// Pre-select the category in the filter
this.selectedCategories = new Set([category]);
this.applyFilters();
this.currentPage = 1;
this.renderProducts();
this.updatePagination();
this.updateResultsCount();
// Update the UI to show the filter is active
this.updateFilterUI(category);
}
}
// Update filter UI to show selected category
updateFilterUI(categoryId) {
// Find the category object to get the display name
const category = this.categories.find((c) => c.id === categoryId);
const displayName = category ? category.name : categoryId;
// Update filter button text to show active filter
const filterToggle = document.getElementById("filter-toggle");
if (filterToggle) {
const filterText = filterToggle.querySelector("span:last-child");
if (filterText) {
filterText.textContent = `Filter: ${displayName}`;
}
}
// Check the corresponding checkbox in the dropdown
setTimeout(() => {
const checkboxes = document.querySelectorAll(".category-checkbox");
checkboxes.forEach((checkbox) => {
if (checkbox.value === categoryId) {
checkbox.checked = true;
}
});
}, 100);
}
// Update filter button text based on selected categories
updateFilterButtonText() {
const filterToggle = document.getElementById("filter-toggle");
if (!filterToggle) return;
const filterText = filterToggle.querySelector("span:last-child");
if (!filterText) return;
// If no categories selected or "all" is selected, show default text
if (
this.selectedCategories.size === 0 ||
this.selectedCategories.has("all")
) {
filterText.textContent = "Filter";
return;
}
// Get the first selected category and find its display name
const firstSelectedCategory = Array.from(this.selectedCategories)[0];
const category = this.categories.find(
(c) => c.id === firstSelectedCategory
);
if (category) {
filterText.textContent = `Filter: ${category.name}`;
} else {
filterText.textContent = "Filter";
}
}
// Render products in the grid
renderProducts(productsToRender = null) {
const productGrid = document.getElementById("product-grid");
if (!productGrid) return;
// Use temporary products if provided, otherwise use filtered products
let sourceProducts = productsToRender || this.filteredProducts;
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
const endIndex = startIndex + this.itemsPerPage;
const productsToShow = sourceProducts.slice(startIndex, endIndex);
productGrid.innerHTML = productsToShow
.map((product) => this.createProductCard(product))
.join("");
this.updateResultsCount();
this.updatePagination();
// Re-add image enlargement listeners and lazy loading after products are rendered
setTimeout(() => {
addImageEnlargementListeners();
this.initLazyLoading();
}, 50);
}
// Create individual product card HTML
createProductCard(product) {
// Check if we're in comparison mode
const urlParams = new URLSearchParams(window.location.search);
const returnTo = urlParams.get("returnTo");
const isComparisonMode = returnTo === "comparison";
return `
<div class="group relative bg-light-bg rounded-lg overflow-hidden hover:shadow-lg transition-shadow product-card" data-product-id="${
product.id
}">
<div class="relative h-80 overflow-hidden">
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='320' viewBox='0 0 400 320'%3E%3Crect width='400' height='320' fill='%23f3f4f6'/%3E%3C/svg%3E"
data-src="${fixImagePath(product.image)}"
alt="${product.alt}"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300 cursor-pointer lazy-load"
data-enlarge-src="${fixImagePath(product.image)}"
loading="lazy"
/>
<!-- Hover Overlay -->
<div class="absolute inset-0 bg-dark-charcoal bg-opacity-70 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
<div class="text-center">
<button
class="bg-white text-uc-gold font-poppins font-semibold px-8 py-3 rounded-md hover:bg-uc-gold hover:text-white transition-colors ${
isComparisonMode ? "cursor-pointer" : ""
}"
onclick="productManager.viewProduct(${product.id})"
>
${isComparisonMode ? "Add to Comparison" : "View"}
</button>
</div>
</div>
</div>
<div class="p-6">
<h3 class="font-poppins font-semibold text-2xl text-dark-charcoal mb-2">
${product.name}
</h3>
<p class="font-poppins font-medium text-base text-quick-silver">
${product.description}
</p>
</div>
</div>
`;
}
// Filter products by category
filterByCategory(category) {
// Single category helper (not used directly by UI)
this.selectedCategories = new Set([category]);
this.applyFilters();
this.currentPage = 1;
this.renderProducts();
this.updatePagination();
}
// Search products
searchProducts(query) {
if (!query.trim()) {
this.filteredProducts = [...this.products];
} else {
this.filteredProducts = this.products.filter(
(product) =>
product.name.toLowerCase().includes(query.toLowerCase()) ||
product.description.toLowerCase().includes(query.toLowerCase())
);
}
this.currentPage = 1;
this.renderProducts();
this.updatePagination();
}
// Apply selected category filters
applyFilters() {
if (
this.selectedCategories.size === 0 ||
this.selectedCategories.has("all")
) {
this.filteredProducts = [...this.products];
return;
}
this.filteredProducts = this.products.filter((product) =>
this.selectedCategories.has(product.category)
);
}
// Sort products
sortProducts(sortBy) {
switch (sortBy) {
case "name-asc":
this.filteredProducts.sort((a, b) => a.name.localeCompare(b.name));
break;
case "name-desc":
this.filteredProducts.sort((a, b) => b.name.localeCompare(a.name));
break;
default:
// Default sorting by ID
this.filteredProducts.sort((a, b) => a.id - b.id);
}
this.renderProducts();
}
// Change page
changePage(page) {
this.currentPage = page;
this.renderProducts();
this.updatePagination();
}
// Update pagination controls
updatePagination() {
const totalPages = Math.ceil(
this.filteredProducts.length / this.itemsPerPage
);
const paginationContainer = document.getElementById("pagination");
if (!paginationContainer) return;
let paginationHTML = "";
for (let i = 1; i <= totalPages; i++) {
const isActive = i === this.currentPage;
paginationHTML += `
<button
class="w-20 h-15 ${
isActive
? "bg-uc-gold text-white"
: "bg-floral-white text-black hover:bg-uc-gold hover:text-white"
} font-poppins font-normal text-xl rounded-lg flex items-center justify-center transition-colors"
onclick="productManager.changePage(${i})"
>
${i}
</button>
`;
}
if (totalPages > 1 && this.currentPage < totalPages) {
paginationHTML += `
<button
class="w-28 h-15 bg-floral-white text-black font-poppins font-light text-xl rounded-lg flex items-center justify-center hover:bg-uc-gold hover:text-white transition-colors"
onclick="productManager.changePage(${this.currentPage + 1})"
>
Next
</button>
`;
}
paginationContainer.innerHTML = paginationHTML;
}
// Build category multi-select dropdown
renderCategoryFilters() {
const container = document.getElementById("filter-categories");
if (!container) return;
const categoryOptions = this.categories
.map(
(c) => `
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" value="${c.id}" class="category-checkbox category-specific">
<span class="font-poppins text-sm text-black">${c.name}</span>
</label>
`
)
.join("");
const allOption = `
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" value="all" class="category-checkbox category-all">
<span class="font-poppins text-sm text-black">All</span>
</label>
`;
container.innerHTML = allOption + categoryOptions;
// Add event listeners for "All" checkbox behavior
const allCheckbox = container.querySelector(".category-all");
const specificCheckboxes = container.querySelectorAll(".category-specific");
if (allCheckbox) {
allCheckbox.addEventListener("change", (e) => {
const isChecked = e.target.checked;
specificCheckboxes.forEach((checkbox) => {
checkbox.checked = isChecked;
});
});
}
// Update "All" checkbox when specific categories change
specificCheckboxes.forEach((checkbox) => {
checkbox.addEventListener("change", () => {
const allChecked = Array.from(specificCheckboxes).every(
(c) => c.checked
);
const anyChecked = Array.from(specificCheckboxes).some(
(c) => c.checked
);
if (allChecked) {
allCheckbox.checked = true;
} else if (!anyChecked) {
allCheckbox.checked = false;
}
});
});
}
// View product details
viewProduct(productId) {
const product = this.products.find((p) => p.id === productId);
if (product) {
// Check if we're in comparison mode
const urlParams = new URLSearchParams(window.location.search);
const returnTo = urlParams.get("returnTo");
const slot = urlParams.get("slot");
const product1Id = urlParams.get("product1");
const product2Id = urlParams.get("product2");
if (returnTo === "comparison" && slot) {
// Navigate back to comparison page with the selected product
let comparisonUrl = "product-comparison.html?";
if (slot === "1") {
// Replace product 1
comparisonUrl += `product1=${productId}`;
if (product2Id) {
comparisonUrl += `&product2=${product2Id}`;
}
} else {
// Replace product 2
if (product1Id) {
comparisonUrl += `product1=${product1Id}&`;
}
comparisonUrl += `product2=${productId}`;
}
console.log("Navigating to comparison page:", comparisonUrl);
window.location.href = comparisonUrl;
} else {
// Normal mode - navigate to product detail page
window.location.href = `../product-detail.html?id=${productId}`;
}
}
}
// Setup event listeners
setupEventListeners() {
// Filter dropdown toggle and outside click
const filterToggle = document.getElementById("filter-toggle");
const filterDropdown = document.getElementById("filter-dropdown");
if (filterToggle && filterDropdown) {
filterToggle.addEventListener("click", () => {
filterDropdown.classList.toggle("hidden");
});
document.addEventListener("click", (e) => {
if (
!filterDropdown.contains(e.target) &&
!filterToggle.contains(e.target)
) {
filterDropdown.classList.add("hidden");
}
});
}
// Sort dropdown
const sortSelect = document.querySelector("select");
if (sortSelect) {
sortSelect.addEventListener("change", (e) => {
this.sortProducts(e.target.value);
});
}
// Apply/clear category filters
const applyBtn = document.getElementById("filter-apply");
const clearBtn = document.getElementById("filter-clear");
if (applyBtn) {
applyBtn.addEventListener("click", () => {
const checks = Array.from(
document.querySelectorAll(".category-checkbox")
);
this.selectedCategories = new Set(
checks.filter((c) => c.checked).map((c) => c.value)
);
this.currentPage = 1;
this.applyFilters();
this.renderProducts();
this.updatePagination();
this.updateResultsCount();
// Update filter button text to show selected category
this.updateFilterButtonText();
const dropdown = document.getElementById("filter-dropdown");
if (dropdown) dropdown.classList.add("hidden");
});
}
if (clearBtn) {
clearBtn.addEventListener("click", () => {
const checks = Array.from(
document.querySelectorAll(".category-checkbox")
);
checks.forEach((c) => (c.checked = false));
this.selectedCategories.clear();
this.currentPage = 1;
this.applyFilters();
this.renderProducts();
this.updatePagination();
this.updateResultsCount();
// Update filter button text to show default
this.updateFilterButtonText();
});
}
// Initialize lazy loading
this.initLazyLoading();
}
// Initialize lazy loading for images
initLazyLoading() {
// Use Intersection Observer for better performance
if ("IntersectionObserver" in window) {
const imageObserver = new IntersectionObserver(
(entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
if (img.dataset.src) {
img.src = img.dataset.src;
img.classList.remove("lazy-load");
img.classList.add("loaded");
observer.unobserve(img);
}
}
});
},
{
rootMargin: "50px 0px", // Start loading 50px before image comes into view
threshold: 0.01,
}
);
// Observe all lazy-loaded images
const lazyImages = document.querySelectorAll(".lazy-load");
lazyImages.forEach((img) => imageObserver.observe(img));
} else {
// Fallback for older browsers - load all images immediately
const lazyImages = document.querySelectorAll(".lazy-load");
lazyImages.forEach((img) => {
if (img.dataset.src) {
img.src = img.dataset.src;
img.classList.remove("lazy-load");
img.classList.add("loaded");
}
});
}
}
// Update results count
updateResultsCount() {
const resultsElement = document.querySelector(".text-quick-silver");
if (resultsElement) {
const startIndex = (this.currentPage - 1) * this.itemsPerPage + 1;
const endIndex = Math.min(
startIndex + this.itemsPerPage - 1,
this.filteredProducts.length
);
resultsElement.textContent = `Showing ${startIndex}${endIndex} of ${this.filteredProducts.length} results`;
}
}
}
// Initialize product manager
const productManager = new ProductManager();
window.productManager = productManager; // Make it globally available
// Image enlargement modal functionality
function addImageEnlargementListeners() {
const productImages = document.querySelectorAll(
"#product-grid img[data-enlarge-src]"
);
const modal = document.getElementById("image-modal");
const modalImage = document.getElementById("modal-image");
const modalCloseBtn = document.getElementById("modal-close-btn");
if (!modal || !modalImage || !modalCloseBtn) return;
productImages.forEach((img) => {
img.addEventListener("click", function (e) {
e.preventDefault(); // Prevent any default behavior
e.stopPropagation(); // Stop event bubbling to parent elements
const imageSrc = this.getAttribute("data-enlarge-src");
const imageAlt = this.getAttribute("alt");
modalImage.src = imageSrc;
modalImage.alt = imageAlt;
// Show modal with animation
modal.classList.remove("hidden");
document.body.style.overflow = "hidden"; // Prevent background scrolling
// Trigger animation after a brief delay
setTimeout(() => {
modalImage.classList.remove("scale-95");
modalImage.classList.add("scale-100");
}, 10);
});
});
// Close modal functionality
function closeModal() {
// Animate out
modalImage.classList.remove("scale-100");
modalImage.classList.add("scale-95");
// Hide modal after animation
setTimeout(() => {
modal.classList.add("hidden");
document.body.style.overflow = ""; // Restore scrolling
}, 300);
}
modalCloseBtn.addEventListener("click", closeModal);
// Close modal when clicking outside the image
modal.addEventListener("click", function (e) {
if (e.target === modal) {
closeModal();
}
});
// Close modal with Escape key
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && !modal.classList.contains("hidden")) {
closeModal();
}
});
}
// Load products when DOM is ready
document.addEventListener("DOMContentLoaded", () => {
productManager.loadProducts();
// Add image enlargement listeners after products are loaded
setTimeout(() => {
addImageEnlargementListeners();
}, 100);
});

View file

@ -1,91 +0,0 @@
const fs = require("fs");
const path = require("path");
const productsPath = path.join(__dirname, "../data/products.json");
const db = JSON.parse(fs.readFileSync(productsPath, "utf8"));
// Default content for tabs (copy matches current UI text style)
const defaultLong = [
"Embodying the raw, wayward spirit of rock n roll, the Kilburn portable active stereo speaker takes the unmistakable look and sound of Marshall, unplugs the chords, and takes the show on the road.",
"Weighing in under 7 pounds, the Kilburn is a lightweight piece of vintage styled engineering. Setting the bar as one of the loudest speakers in its class, the Kilburn is a compact, stout-hearted hero with a well-balanced audio which boasts a clear midrange and extended highs for a sound that is both articulate and pronounced. The analogue knobs allow you to fine tune the controls to your personal preferences while the guitar-influenced leather strap enables easy and stylish travel.",
];
const defaultsByCategory = {
seating: {
additionalInformation: {
Material: "Fabric, engineered wood base",
Upholstery: "Performance fabric",
Dimensions: "See size options",
Warranty: "2 years",
},
},
tables: {
additionalInformation: {
Material: "Engineered wood, steel frame",
Finish: "Matte laminate",
Dimensions: "Small/Medium/Large",
Warranty: "2 years",
},
},
storage: {
additionalInformation: {
Material: "Powder-coated steel",
Capacity: "Modular shelves",
Dimensions: "Standard/Large/XL",
Warranty: "2 years",
},
},
workspace: {
additionalInformation: {
Material: "Acoustic panels, aluminum frame",
Power: "Integrated power module",
Dimensions: "Standard/Large",
Warranty: "2 years",
},
},
};
function ensureFields(p) {
const cat =
p.category && defaultsByCategory[p.category] ? p.category : "seating";
const d = defaultsByCategory[cat];
if (!Array.isArray(p.descriptionLong) || p.descriptionLong.length === 0) {
p.descriptionLong = defaultLong;
}
if (
typeof p.additionalInformation !== "object" ||
p.additionalInformation === null
) {
p.additionalInformation = { ...d.additionalInformation };
} else {
// Fill any missing keys with defaults without overwriting existing
for (const [k, v] of Object.entries(d.additionalInformation)) {
if (
p.additionalInformation[k] == null ||
p.additionalInformation[k] === ""
) {
p.additionalInformation[k] = v;
}
}
}
// Ensure reviewsCount mirrors numeric reviews
const reviewsNum = Number(p.reviews || 0);
p.reviews = Number.isFinite(reviewsNum) ? reviewsNum : 0;
if (p.reviewsCount == null) p.reviewsCount = p.reviews;
// Ensure galleryPairs (two wide images for tabs). Use product image as fallback.
if (!Array.isArray(p.galleryPairs) || p.galleryPairs.length === 0) {
const img = p.image || "../assets/images/asgaard_sofa.png";
p.galleryPairs = [{ left: img, right: img }];
}
}
(db.products || []).forEach(ensureFields);
fs.writeFileSync(productsPath, JSON.stringify(db, null, 2));
console.log(
"Ensured tabs data for all products: descriptionLong, additionalInformation, reviewsCount, galleryPairs"
);

View file

@ -1,56 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./*.html", "./src/**/*.{html,js}", "./scripts/**/*.js"],
theme: {
screens: {
sm: "640px",
md: "768px",
lg: "1024px",
xl: "1280px",
"2xl": "1536px",
},
extend: {
fontFamily: {
montserrat: ["Montserrat", "sans-serif"],
playfair: ["Playfair Display", "serif"],
},
colors: {
white: "#FFFFFF",
"dark-charcoal": "#2F2F2F",
black: "#000000",
"uc-gold": "#B8873F",
"floral-white": "#FCF8F3",
axolotl: "#6F776B",
"dark-charcoal-ii": "#212121",
"eerie-black": "#1A1A1A",
"taupe-gray": "#888888",
"davys-grey": "#595959",
"granite-gray": "#666666",
"light-silver": "#D7D7D7",
"quick-silver": "#A0A0A0",
linen: "#FAF0E6",
"light-bg": "#F4F5F7",
},
spacing: {
396: "396px",
398: "398px",
420: "420px",
259: "259px",
649: "649px",
183: "183px",
150: "150px",
100: "100px",
120: "120px",
60: "240px",
180: "720px",
360: "1440px",
},
textShadow: {
default: "0 2px 4px rgba(0,0,0,0.1)",
lg: "2px 2px 4px rgba(0,0,0,0.3)",
xl: "4px 4px 8px rgba(0,0,0,0.4)",
},
},
},
plugins: [require("tailwindcss-textshadow")],
};

View file

@ -1,728 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Product Management - KHY Admin</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
.admin-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.product-card {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 4px;
font-weight: 600;
}
.form-group input,
.form-group textarea,
.form-group select {
width: 100%;
padding: 8px;
border: 1px solid #d1d5db;
border-radius: 4px;
}
.btn {
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
border: none;
}
.btn-primary {
background: #3b82f6;
color: white;
}
.btn-danger {
background: #ef4444;
color: white;
}
.btn-success {
background: #10b981;
color: white;
}
.color-input {
width: 60px;
height: 40px;
border: none;
border-radius: 4px;
}
.image-preview {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 4px;
}
</style>
</head>
<body class="bg-gray-50">
<div class="admin-container">
<h1 class="text-3xl font-bold mb-8">Product Management</h1>
<!-- Add New Product Form -->
<div class="bg-white p-6 rounded-lg shadow mb-8">
<h2 class="text-xl font-semibold mb-4">Add New Product</h2>
<!-- Preview Section -->
<div
id="previewSection"
class="hidden mb-6 p-4 bg-gray-50 rounded-lg border"
>
<h3 class="font-semibold mb-3">Preview</h3>
<div id="previewContent"></div>
</div>
<form id="productForm">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-group">
<label>Product Name</label>
<input type="text" id="productName" required />
</div>
<div class="form-group">
<label>Category</label>
<select id="productCategory" required>
<option value="seating">Seating</option>
<option value="tables">Tables</option>
<option value="storage">Storage</option>
<option value="workspace">Workspace</option>
</select>
</div>
<div class="form-group">
<label>Price</label>
<input type="number" id="productPrice" step="0.01" required />
</div>
<div class="form-group">
<label>Model Number</label>
<input type="text" id="productModel" />
</div>
<div class="form-group">
<label>Short Description</label>
<textarea id="productDescription" rows="2"></textarea>
</div>
<div class="form-group">
<label>Main Image URL</label>
<input
type="text"
id="productImage"
placeholder="assets/images/product.jpg"
/>
</div>
<div class="form-group">
<label>In Stock</label>
<select id="productInStock">
<option value="true">Yes</option>
<option value="false">No</option>
</select>
</div>
<div class="form-group">
<label>Rating</label>
<input
type="number"
id="productRating"
step="0.1"
min="0"
max="5"
value="4.0"
/>
</div>
</div>
<div class="mt-4">
<h3 class="font-semibold mb-2">Available Sizes</h3>
<div id="sizesContainer">
<div class="flex gap-2 mb-2">
<input
type="text"
placeholder="Size (e.g., S, M, L)"
class="size-input"
/>
<button
type="button"
onclick="removeSize(this)"
class="btn btn-danger"
>
Remove
</button>
</div>
</div>
<button type="button" onclick="addSize()" class="btn btn-primary">
Add Size
</button>
</div>
<div class="mt-4">
<h3 class="font-semibold mb-2">Available Colors</h3>
<div id="colorsContainer">
<div class="flex gap-2 mb-2 items-center">
<input
type="text"
placeholder="Color Name"
class="color-name"
/>
<input type="color" class="color-input" value="#000000" />
<button
type="button"
onclick="removeColor(this)"
class="btn btn-danger"
>
Remove
</button>
</div>
</div>
<button type="button" onclick="addColor()" class="btn btn-primary">
Add Color
</button>
</div>
<div class="mt-6 flex gap-4">
<button type="submit" class="btn btn-success">Add Product</button>
<button
type="button"
onclick="previewProduct()"
class="btn btn-primary"
>
Preview Product
</button>
<button
type="button"
onclick="cancelEdit()"
class="btn btn-danger hidden"
id="cancelEditBtn"
>
Cancel Edit
</button>
</div>
</form>
</div>
<!-- Products List -->
<div class="bg-white p-6 rounded-lg shadow">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold">Current Products</h2>
<div class="flex gap-2">
<button onclick="previewWebsite()" class="btn btn-primary">
👀 Preview Website
</button>
<button onclick="downloadProductsJSON()" class="btn btn-primary">
Download products.json
</button>
<button onclick="deployToProduction()" class="btn btn-success">
🚀 Copy to Main Website
</button>
</div>
</div>
<div id="productsList"></div>
</div>
</div>
<script>
let products = [];
// Load products on page load
async function loadProducts() {
try {
const response = await fetch("data/products.json");
const data = await response.json();
products = data.products;
displayProducts();
} catch (error) {
console.error("Error loading products:", error);
alert("Error loading products. Please refresh the page.");
}
}
// Display products
function displayProducts() {
const container = document.getElementById("productsList");
container.innerHTML = "";
products.forEach((product, index) => {
const productCard = document.createElement("div");
productCard.className = "product-card";
productCard.innerHTML = `
<div class="flex justify-between items-start">
<div class="flex gap-4">
<img src="${product.image}" alt="${product.name}" class="image-preview" onerror="this.src='assets/images/placeholder.jpg'">
<div>
<h3 class="font-semibold">${product.name}</h3>
<p class="text-gray-600">${product.category} • $${product.price}</p>
<p class="text-sm text-gray-500">${product.description}</p>
</div>
</div>
<div class="flex gap-2">
<button onclick="editProduct(${index})" class="btn btn-primary">Edit</button>
<button onclick="deleteProduct(${index})" class="btn btn-danger">Delete</button>
</div>
</div>
`;
container.appendChild(productCard);
});
}
// Add new product or update existing
document
.getElementById("productForm")
.addEventListener("submit", function (e) {
e.preventDefault();
const sizes = Array.from(document.querySelectorAll(".size-input"))
.map((input) => input.value)
.filter((size) => size.trim());
const colors = Array.from(document.querySelectorAll(".color-name"))
.map((input, index) => {
const colorInput =
input.parentElement.querySelector(".color-input");
return {
name: input.value,
value: colorInput.value,
selected: index === 0,
};
})
.filter((color) => color.name.trim());
const productData = {
name: document.getElementById("productName").value,
description: document.getElementById("productDescription").value,
image: document.getElementById("productImage").value,
alt: document.getElementById("productName").value,
category: document.getElementById("productCategory").value,
modelNo: document.getElementById("productModel").value,
tags: [document.getElementById("productCategory").value],
sizes: sizes,
colors: colors,
selectedSize: sizes[0] || "M",
selectedColor: colors[0]?.name || "Default",
price: parseFloat(document.getElementById("productPrice").value),
originalPrice: parseFloat(
document.getElementById("productPrice").value
),
rating: document.getElementById("productRating").value,
reviews: 0,
inStock: document.getElementById("productInStock").value === "true",
images: [document.getElementById("productImage").value],
descriptionLong: [
document.getElementById("productDescription").value,
],
additionalInformation: {
Material: "Premium materials",
Dimensions: "See size options",
Warranty: "3 years",
},
reviewsCount: 0,
galleryPairs: [
{
left: document.getElementById("productImage").value,
right: document.getElementById("productImage").value,
},
],
dimensions: "See size options",
salesPackage: "1 unit",
configuration: "Standard",
fillingMaterial: "High-density foam",
finishType: "Premium finish",
adjustableHeadrest: "No",
maxLoadCapacity: "150kg",
originOfManufacture: "Ghana",
weight: "25kg",
seatHeight: "45cm",
legHeight: "10cm",
warrantyServiceType: "Standard warranty service",
coveredInWarranty: "Manufacturing defects",
notCoveredInWarranty: "Wear and tear not covered",
};
const form = document.getElementById("productForm");
const editIndex = form.dataset.editIndex;
if (editIndex !== undefined) {
// Update existing product
productData.id = products[editIndex].id;
products[editIndex] = productData;
alert("Product updated successfully!");
// Reset form to add mode
form.dataset.editIndex = "";
form.querySelector("button[type='submit']").textContent =
"Add Product";
} else {
// Add new product
productData.id = Math.max(...products.map((p) => p.id)) + 1;
products.push(productData);
alert("Product added successfully!");
}
displayProducts();
document.getElementById("productForm").reset();
hidePreview();
});
// Helper functions
function addSize() {
const container = document.getElementById("sizesContainer");
const div = document.createElement("div");
div.className = "flex gap-2 mb-2";
div.innerHTML = `
<input type="text" placeholder="Size (e.g., S, M, L)" class="size-input">
<button type="button" onclick="removeSize(this)" class="btn btn-danger">Remove</button>
`;
container.appendChild(div);
}
function removeSize(button) {
button.parentElement.remove();
}
function addColor() {
const container = document.getElementById("colorsContainer");
const div = document.createElement("div");
div.className = "flex gap-2 mb-2 items-center";
div.innerHTML = `
<input type="text" placeholder="Color Name" class="color-name">
<input type="color" class="color-input" value="#000000">
<button type="button" onclick="removeColor(this)" class="btn btn-danger">Remove</button>
`;
container.appendChild(div);
}
function removeColor(button) {
button.parentElement.remove();
}
function editProduct(index) {
const product = products[index];
// Populate form with existing product data
document.getElementById("productName").value = product.name;
document.getElementById("productDescription").value =
product.description;
document.getElementById("productImage").value = product.image;
document.getElementById("productCategory").value = product.category;
document.getElementById("productPrice").value = product.price;
document.getElementById("productModel").value = product.modelNo || "";
document.getElementById("productInStock").value =
product.inStock.toString();
document.getElementById("productRating").value = product.rating;
// Clear existing sizes and colors
document.getElementById("sizesContainer").innerHTML = "";
document.getElementById("colorsContainer").innerHTML = "";
// Add existing sizes
product.sizes.forEach((size) => {
addSize();
const sizeInputs = document.querySelectorAll(".size-input");
sizeInputs[sizeInputs.length - 1].value = size;
});
// Add existing colors
product.colors.forEach((color) => {
addColor();
const colorInputs = document.querySelectorAll(".color-name");
const colorPickers = document.querySelectorAll(".color-input");
colorInputs[colorInputs.length - 1].value = color.name;
colorPickers[colorPickers.length - 1].value = color.value;
});
// Change form to edit mode
const form = document.getElementById("productForm");
form.dataset.editIndex = index;
form.querySelector("button[type='submit']").textContent =
"Update Product";
document.getElementById("cancelEditBtn").classList.remove("hidden");
// Scroll to form
form.scrollIntoView({ behavior: "smooth" });
// Show preview
showPreview(product);
}
function deleteProduct(index) {
const product = products[index];
const confirmMessage = `Are you sure you want to delete "${product.name}"?\n\nThis action cannot be undone.`;
if (confirm(confirmMessage)) {
products.splice(index, 1);
displayProducts();
alert(`"${product.name}" has been deleted successfully!`);
}
}
function previewProduct() {
const sizes = Array.from(document.querySelectorAll(".size-input"))
.map((input) => input.value)
.filter((size) => size.trim());
const colors = Array.from(document.querySelectorAll(".color-name"))
.map((input, index) => {
const colorInput =
input.parentElement.querySelector(".color-input");
return {
name: input.value,
value: colorInput.value,
selected: index === 0,
};
})
.filter((color) => color.name.trim());
const previewProduct = {
name: document.getElementById("productName").value || "Product Name",
description:
document.getElementById("productDescription").value ||
"Product description",
image:
document.getElementById("productImage").value ||
"assets/images/placeholder.jpg",
category:
document.getElementById("productCategory").value || "seating",
price: document.getElementById("productPrice").value || "0",
modelNo: document.getElementById("productModel").value || "",
sizes: sizes,
colors: colors,
inStock: document.getElementById("productInStock").value === "true",
rating: document.getElementById("productRating").value || "4.0",
};
showPreview(previewProduct);
}
function showPreview(product) {
const previewSection = document.getElementById("previewSection");
const previewContent = document.getElementById("previewContent");
previewContent.innerHTML = `
<div class="flex gap-4">
<img src="${product.image}" alt="${
product.name
}" class="w-24 h-24 object-cover rounded" onerror="this.src='assets/images/placeholder.jpg'">
<div class="flex-1">
<h4 class="font-semibold text-lg">${product.name}</h4>
<p class="text-gray-600">${product.category} • $${
product.price
}</p>
<p class="text-sm text-gray-500 mb-2">${product.description}</p>
<div class="text-xs text-gray-400">
<p><strong>Model:</strong> ${product.modelNo || "N/A"}</p>
<p><strong>Sizes:</strong> ${
product.sizes.length > 0 ? product.sizes.join(", ") : "None"
}</p>
<p><strong>Colors:</strong> ${
product.colors.length > 0
? product.colors.map((c) => c.name).join(", ")
: "None"
}</p>
<p><strong>In Stock:</strong> ${
product.inStock ? "Yes" : "No"
}</p>
<p><strong>Rating:</strong> ${product.rating}/5</p>
</div>
</div>
</div>
`;
previewSection.classList.remove("hidden");
previewSection.scrollIntoView({ behavior: "smooth", block: "nearest" });
}
function hidePreview() {
const previewSection = document.getElementById("previewSection");
previewSection.classList.add("hidden");
}
function cancelEdit() {
const form = document.getElementById("productForm");
form.dataset.editIndex = "";
form.querySelector("button[type='submit']").textContent = "Add Product";
document.getElementById("cancelEditBtn").classList.add("hidden");
document.getElementById("productForm").reset();
hidePreview();
}
function downloadProductsJSON() {
const data = {
products: products,
categories: [
{
id: "seating",
name: "Seating",
description:
"Office chairs, lounge chairs, and seating solutions",
},
{
id: "tables",
name: "Tables",
description: "Conference tables, workstations, and dining tables",
},
{
id: "storage",
name: "Storage",
description:
"Storage units, lockers, and organizational solutions",
},
{
id: "workspace",
name: "Workspace",
description: "Pods, phone booths, and collaborative spaces",
},
],
pagination: {
itemsPerPage: 16,
totalItems: products.length,
currentPage: 1,
totalPages: Math.ceil(products.length / 16),
},
};
const blob = new Blob([JSON.stringify(data, null, 2)], {
type: "application/json",
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "products.json";
a.click();
URL.revokeObjectURL(url);
alert(`Downloaded products.json with ${products.length} products!`);
}
function previewWebsite() {
const baseUrl =
window.location.origin +
window.location.pathname.replace("admin.html", "");
const previewMessage = `Preview Website\n\nThis will help you preview how your products will look on the website.\n\nChoose your preferred method:\n\nOption 1: Auto-start server (Recommended)\n- Click OK to get instructions for running: npm run preview\n- This will start the server and open all preview pages automatically\n\nOption 2: Open preview pages now\n- Click Cancel to open preview pages in current browser\n- Make sure you have a server running first`;
if (confirm(previewMessage)) {
// Show instructions for auto-start
alert(
`🚀 Auto-Start Preview Server\n\nTo automatically start the server and open preview pages:\n\n1. Open Terminal/Command Prompt\n2. Navigate to the admin folder\n3. Run: npm run preview\n\nThis will:\n- Start the local server\n- Open admin dashboard\n- Open homepage preview\n- Open product catalog preview\n- Open product details preview\n\nAll in one command! 🎉`
);
} else {
// Open preview pages in current browser
const pages = [
{ url: "index.html", name: "Homepage" },
{ url: "product-catalog.html", name: "Product Catalog" },
{ url: "product-detail.html", name: "Product Details" },
];
pages.forEach((page, index) => {
setTimeout(() => {
window.open(page.url, `_blank`);
}, index * 500); // Stagger opening to avoid popup blockers
});
// Show helpful message
setTimeout(() => {
alert(
`👀 Preview Pages Opened\n\nOpened in new tabs:\n- Homepage\n- Product Catalog\n- Product Details\n\nIf tabs didn't open, check your popup blocker settings.\n\n💡 For the best experience, run: npm run preview`
);
}, 2000);
}
}
function deployToProduction() {
const confirmMessage = `Copy to Main Website\n\nThis will copy your updated product data to the main KHY website.\n\nAre you sure you want to proceed?\n\nThis will:\n- Stop the preview server (if running)\n- Copy admin/data/products.json → ../data/products.json\n- Copy admin/assets/images/ → ../assets/images/\n- Create a backup of current main website data\n- Update the main KHY website immediately\n\nMake sure you've previewed your changes first!`;
if (confirm(confirmMessage)) {
// Show deployment instructions
alert(
`Copy Instructions\n\nTo copy your changes to the main KHY website:\n\n1. Open Terminal/Command Prompt\n2. Navigate to the admin folder\n3. Run: npm run deploy\n\nThis will:\n- Automatically stop the preview server\n- Copy admin/data/products.json → main website data/products.json\n- Copy admin/assets/images/ → main website assets/images/\n\nYour main KHY website will be updated immediately!\n\nThe preview server will be automatically stopped during deployment.\n\nNote: If this is your first time, run 'npm run setup' first to configure the deploy paths.`
);
}
}
// Server management
let previewTabs = [];
// Function to kill preview server
function killPreviewServer() {
// This would need to be implemented with a backend endpoint
// For now, we'll show instructions
alert(
`Stop Preview Server\n\nTo stop the preview server:\n\n1. Open Terminal/Command Prompt\n2. Navigate to the admin folder\n3. Run: npm run preview:kill\n\nOr press Ctrl+C in the terminal where the server is running.`
);
}
// Function to track preview tabs
function trackPreviewTab(tab) {
previewTabs.push(tab);
// Check if tab is closed
const checkClosed = setInterval(() => {
if (tab.closed) {
previewTabs = previewTabs.filter((t) => t !== tab);
clearInterval(checkClosed);
// If no preview tabs are open, offer to kill server
if (previewTabs.length === 0) {
setTimeout(() => {
if (
confirm(
`All Preview Tabs Closed\n\nAll preview tabs have been closed.\n\nWould you like to stop the preview server?\n\nThis will free up the port for other uses.`
)
) {
killPreviewServer();
}
}, 1000);
}
}
}, 1000);
}
// Enhanced preview function with tab tracking
function previewWebsiteWithTracking() {
const baseUrl =
window.location.origin +
window.location.pathname.replace("admin.html", "");
const previewMessage = `Preview Website\n\nThis will help you preview how your products will look on the website.\n\nChoose your preferred method:\n\nOption 1: Auto-start server (Recommended)\n- Click OK to get instructions for running: npm run preview\n- This will start the server and open all preview pages automatically\n\nOption 2: Open preview pages now\n- Click Cancel to open preview pages in current browser\n- Make sure you have a server running first`;
if (confirm(previewMessage)) {
// Show instructions for auto-start
alert(
`Auto-Start Preview Server\n\nTo automatically start the server and open preview pages:\n\n1. Open Terminal/Command Prompt\n2. Navigate to the admin folder\n3. Run: npm run preview\n\nThis will:\n- Start the local server\n- Open admin dashboard\n- Open homepage preview\n- Open product catalog preview\n- Open product details preview\n\nAll in one command!\n\nThe server will automatically stop when you close preview tabs or deploy changes.`
);
} else {
// Open preview pages in current browser with tracking
const pages = [
{ url: "index.html", name: "Homepage" },
{ url: "product-catalog.html", name: "Product Catalog" },
{ url: "product-detail.html", name: "Product Details" },
];
pages.forEach((page, index) => {
setTimeout(() => {
const tab = window.open(page.url, `_blank`);
if (tab) {
trackPreviewTab(tab);
}
}, index * 500); // Stagger opening to avoid popup blockers
});
// Show helpful message
setTimeout(() => {
alert(
`Preview Pages Opened\n\nOpened in new tabs:\n- Homepage\n- Product Catalog\n- Product Details\n\nIf tabs didn't open, check your popup blocker settings.\n\nFor the best experience, run: npm run preview\n\nThe server will be automatically stopped when you close all preview tabs.`
);
}, 2000);
}
}
// Override the original preview function
window.previewWebsite = previewWebsiteWithTracking;
// Load products when page loads
loadProducts();
</script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 548 B

View file

Before

Width:  |  Height:  |  Size: 815 B

After

Width:  |  Height:  |  Size: 815 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 889 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 500 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 802 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

View file

Before

Width:  |  Height:  |  Size: 6.3 MiB

After

Width:  |  Height:  |  Size: 6.3 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 815 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 KiB

View file

Before

Width:  |  Height:  |  Size: 417 KiB

After

Width:  |  Height:  |  Size: 417 KiB

Before After
Before After

Some files were not shown because too many files have changed in this diff Show more