From fd3ba36ae2528a355e8489361f2a4f41d703d2a3 Mon Sep 17 00:00:00 2001 From: DaX Date: Tue, 5 Aug 2025 23:34:35 +0200 Subject: [PATCH] Add color request feature with modal and Safari styling fixes - Implement color request modal popup instead of separate page - Add Serbian translations throughout - Fix Safari form styling issues with custom CSS - Add 'Other' option to material and finish dropdowns - Create admin panel for managing color requests - Add database migration for color_requests table - Implement API endpoints for color request management --- api/server.js | 122 +++++++ app/page.tsx | 20 + app/upadaj/dashboard/page.tsx | 12 + app/upadaj/requests/page.tsx | 343 ++++++++++++++++++ .../migrations/016_add_color_requests.sql | 35 ++ src/components/ColorRequestForm.tsx | 170 +++++++++ src/components/ColorRequestModal.tsx | 213 +++++++++++ src/services/api.ts | 30 ++ src/styles/index.css | 54 +++ 9 files changed, 999 insertions(+) create mode 100644 app/upadaj/requests/page.tsx create mode 100644 database/migrations/016_add_color_requests.sql create mode 100644 src/components/ColorRequestForm.tsx create mode 100644 src/components/ColorRequestModal.tsx diff --git a/api/server.js b/api/server.js index 04738c4..22d6c77 100644 --- a/api/server.js +++ b/api/server.js @@ -236,6 +236,128 @@ app.post('/api/filaments/sale/bulk', authenticateToken, async (req, res) => { } }); +// Color request endpoints + +// Get all color requests (admin only) +app.get('/api/color-requests', authenticateToken, async (req, res) => { + try { + const result = await pool.query( + 'SELECT * FROM color_requests ORDER BY created_at DESC' + ); + res.json(result.rows); + } catch (error) { + console.error('Error fetching color requests:', error); + res.status(500).json({ error: 'Failed to fetch color requests' }); + } +}); + +// Submit a new color request (public) +app.post('/api/color-requests', async (req, res) => { + try { + const { + color_name, + material_type, + finish_type, + user_email, + user_name, + description, + reference_url + } = req.body; + + // Check if similar request already exists + const existingRequest = await pool.query( + `SELECT id, request_count FROM color_requests + WHERE LOWER(color_name) = LOWER($1) + AND material_type = $2 + AND (finish_type = $3 OR (finish_type IS NULL AND $3 IS NULL)) + AND status = 'pending'`, + [color_name, material_type, finish_type] + ); + + if (existingRequest.rows.length > 0) { + // Increment request count for existing request + const result = await pool.query( + `UPDATE color_requests + SET request_count = request_count + 1, + updated_at = CURRENT_TIMESTAMP + WHERE id = $1 + RETURNING *`, + [existingRequest.rows[0].id] + ); + res.json({ + message: 'Your request has been added to an existing request for this color', + request: result.rows[0] + }); + } else { + // Create new request + const result = await pool.query( + `INSERT INTO color_requests + (color_name, material_type, finish_type, user_email, user_name, description, reference_url) + VALUES ($1, $2, $3, $4, $5, $6, $7) + RETURNING *`, + [color_name, material_type, finish_type, user_email, user_name, description, reference_url] + ); + res.json({ + message: 'Color request submitted successfully', + request: result.rows[0] + }); + } + } catch (error) { + console.error('Error creating color request:', error); + res.status(500).json({ error: 'Failed to submit color request' }); + } +}); + +// Update color request status (admin only) +app.put('/api/color-requests/:id', authenticateToken, async (req, res) => { + try { + const { id } = req.params; + const { status, admin_notes } = req.body; + + const result = await pool.query( + `UPDATE color_requests + SET status = $1, + admin_notes = $2, + processed_at = CURRENT_TIMESTAMP, + processed_by = $3, + updated_at = CURRENT_TIMESTAMP + WHERE id = $4 + RETURNING *`, + [status, admin_notes, req.user.username, id] + ); + + if (result.rows.length === 0) { + return res.status(404).json({ error: 'Color request not found' }); + } + + res.json(result.rows[0]); + } catch (error) { + console.error('Error updating color request:', error); + res.status(500).json({ error: 'Failed to update color request' }); + } +}); + +// Delete color request (admin only) +app.delete('/api/color-requests/:id', authenticateToken, async (req, res) => { + try { + const { id } = req.params; + + const result = await pool.query( + 'DELETE FROM color_requests WHERE id = $1 RETURNING *', + [id] + ); + + if (result.rows.length === 0) { + return res.status(404).json({ error: 'Color request not found' }); + } + + res.json({ message: 'Color request deleted successfully' }); + } catch (error) { + console.error('Error deleting color request:', error); + res.status(500).json({ error: 'Failed to delete color request' }); + } +}); + app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index f3ce6b9..6f08546 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -3,6 +3,7 @@ import { useState, useEffect } from 'react'; import { FilamentTableV2 } from '../src/components/FilamentTableV2'; import { SaleCountdown } from '../src/components/SaleCountdown'; +import ColorRequestModal from '../src/components/ColorRequestModal'; import { Filament } from '../src/types/filament'; import { filamentService } from '../src/services/api'; import { trackEvent } from '../src/components/MatomoAnalytics'; @@ -14,6 +15,7 @@ export default function Home() { const [darkMode, setDarkMode] = useState(false); const [mounted, setMounted] = useState(false); const [resetKey, setResetKey] = useState(0); + const [showColorRequestModal, setShowColorRequestModal] = useState(false); // Removed V1/V2 toggle - now only using V2 // Initialize dark mode from localStorage after mounting @@ -173,6 +175,19 @@ export default function Home() { Pozovi +381 67 710 2845 + + + {/* Color Request Modal */} + setShowColorRequestModal(false)} + /> ); } \ No newline at end of file diff --git a/app/upadaj/dashboard/page.tsx b/app/upadaj/dashboard/page.tsx index 0f40e8e..72dc5b0 100644 --- a/app/upadaj/dashboard/page.tsx +++ b/app/upadaj/dashboard/page.tsx @@ -377,6 +377,18 @@ export default function AdminDashboard() { selectedFilaments={selectedFilaments} onSaleUpdate={fetchFilaments} /> + +