feat: Implement complete product page with industry best practices

Phase 1 Implementation:
- Horizontal scrollable thumbnail slider with arrow navigation
- Variation selector with auto-image switching
- Enhanced buy section with quantity controls
- Product tabs (Description, Additional Info, Reviews)
- Specifications table from attributes
- Responsive design with mobile optimization

Features:
- Image gallery: Click thumbnails to change main image
- Variation selector: Auto-updates price, stock, and image
- Stock status: Color-coded indicators (green/red)
- Add to cart: Validates variation selection
- Breadcrumb navigation
- Product meta (SKU, categories)
- Wishlist button (UI only)

Documentation:
- PRODUCT_PAGE_SOP.md: Industry best practices guide
- PRODUCT_PAGE_IMPLEMENTATION.md: Implementation plan

Admin:
- Sortable images with visual dropzone indicators
- Dashed borders show drag-and-drop capability
- Ring highlight on drag-over
- Opacity change when dragging

Files changed:
- customer-spa/src/pages/Product/index.tsx: Complete rebuild
- customer-spa/src/index.css: Add scrollbar-hide utility
- admin-spa/src/routes/Products/partials/tabs/GeneralTab.tsx: Enhanced dropzone
This commit is contained in:
Dwindi Ramadhana
2025-11-26 16:29:02 +07:00
parent f397ef850f
commit c37ecb8e96
5 changed files with 1260 additions and 9 deletions

View File

@@ -209,13 +209,22 @@ export function GeneralTab({
onDragStart={(e) => {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', index.toString());
e.currentTarget.classList.add('opacity-50');
}}
onDragEnd={(e) => {
e.currentTarget.classList.remove('opacity-50');
}}
onDragOver={(e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
e.currentTarget.classList.add('ring-2', 'ring-primary', 'ring-offset-2');
}}
onDragLeave={(e) => {
e.currentTarget.classList.remove('ring-2', 'ring-primary', 'ring-offset-2');
}}
onDrop={(e) => {
e.preventDefault();
e.currentTarget.classList.remove('ring-2', 'ring-primary', 'ring-offset-2');
const fromIndex = parseInt(e.dataTransfer.getData('text/plain'));
const toIndex = index;
@@ -226,7 +235,7 @@ export function GeneralTab({
setImages(newImages);
}
}}
className="relative group aspect-square border rounded-lg overflow-hidden bg-gray-50 cursor-move hover:border-primary transition-colors"
className="relative group aspect-square border-2 border-dashed border-gray-300 rounded-lg overflow-hidden bg-gray-50 cursor-move hover:border-primary transition-all"
>
<img
src={image}