diff --git a/Client/package-lock.json b/Client/package-lock.json index 7c71d4c..8eb82e5 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -21,13 +21,14 @@ "@stripe/stripe-js": "^7.2.0", "antd": "^5.24.9", "axios": "^1.10.0", + "chart.js": "^4.5.0", "date-fns": "^2.30.0", "form-data": "^4.0.3", "i18next": "^25.0.1", "i18next-browser-languagedetector": "^8.0.5", "js-cookie": "^3.0.5", "json2csv": "^6.0.0-alpha.2", - "jspdf": "^3.0.1", + "jspdf": "^3.0.2", "leaflet": "^1.9.4", "lodash": "^4.17.21", "lucide-react": "^0.487.0", @@ -818,6 +819,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, "node_modules/@mui/base": { "version": "5.0.0-beta.40-1", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40-1.tgz", @@ -1782,6 +1789,12 @@ "integrity": "sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ==", "license": "MIT" }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", + "license": "MIT" + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -1986,18 +1999,6 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "license": "(MIT OR Apache-2.0)", - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/autoprefixer": { "version": "10.4.21", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", @@ -2148,18 +2149,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/btoa": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", - "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", - "license": "(MIT OR Apache-2.0)", - "bin": { - "btoa": "bin/btoa.js" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2291,6 +2280,18 @@ "license": "MIT", "optional": true }, + "node_modules/chart.js": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", + "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -2848,6 +2849,17 @@ "node": ">= 6" } }, + "node_modules/fast-png": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", + "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==", + "license": "MIT", + "dependencies": { + "@types/pako": "^2.0.3", + "iobuffer": "^5.3.2", + "pako": "^2.1.0" + } + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -3266,6 +3278,12 @@ "loose-envify": "^1.0.0" } }, + "node_modules/iobuffer": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz", + "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==", + "license": "MIT" + }, "node_modules/is-arguments": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", @@ -3509,14 +3527,13 @@ } }, "node_modules/jspdf": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.1.tgz", - "integrity": "sha512-qaGIxqxetdoNnFQQXxTKUD9/Z7AloLaw94fFsOiJMxbfYdBbrBuhWmbzI8TVjrw7s3jBY1PFHofBKMV/wZPapg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.2.tgz", + "integrity": "sha512-G0fQDJ5fAm6UW78HG6lNXyq09l0PrA1rpNY5i+ly17Zb1fMMFSmS+3lw4cnrAPGyouv2Y0ylujbY2Ieq3DSlKA==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.26.7", - "atob": "^2.1.2", - "btoa": "^1.2.1", + "@babel/runtime": "^7.26.9", + "fast-png": "^6.2.0", "fflate": "^0.8.1" }, "optionalDependencies": { @@ -3916,6 +3933,12 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, "node_modules/parchment": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz", diff --git a/Client/package.json b/Client/package.json index 860b158..8b3354a 100644 --- a/Client/package.json +++ b/Client/package.json @@ -21,13 +21,14 @@ "@stripe/stripe-js": "^7.2.0", "antd": "^5.24.9", "axios": "^1.10.0", + "chart.js": "^4.5.0", "date-fns": "^2.30.0", "form-data": "^4.0.3", "i18next": "^25.0.1", "i18next-browser-languagedetector": "^8.0.5", "js-cookie": "^3.0.5", "json2csv": "^6.0.0-alpha.2", - "jspdf": "^3.0.1", + "jspdf": "^3.0.2", "leaflet": "^1.9.4", "lodash": "^4.17.21", "lucide-react": "^0.487.0", diff --git a/Client/src/App.jsx b/Client/src/App.jsx index fc214e1..af4063e 100644 --- a/Client/src/App.jsx +++ b/Client/src/App.jsx @@ -3,20 +3,19 @@ import { Routes, Route, Outlet } from 'react-router-dom'; import ScrollRestoration from './components/ScrollRestoration'; import { AuthProvider } from './components/context/AuthContext'; import HomeScreen from './screens/HomeScreen'; +import Login from './screens/Login'; +import Register from './screens/Register'; import BillingInvoice from './screens/BillingInvoice'; -// import CustomerFeedback from './screens/CustomerFeedback'; -// import InventoryStockManage from './screens/InventoryStockManage'; -// import KitchenDisplay from './screens/KitchenDisplay'; -// import LoyaltyPrograms from './screens/LoyaltyPrograms'; -// import MenuManagement from './screens/MenuManagement'; -// import POSsystem from './screens/POSsystem'; -import RestaurantAnalytics from './screens/RestaurantAnalytics'; import Settings from './screens/Settings'; -// import StaffManagement from './screens/StaffManagement'; +import ProtectedRoute from './components/ProtectedRoute'; import ReservationManagement from './screens/ReservationManagement/ReservationManagement'; -// import WalkinManagement from './screens/WalkinManagement'; import RoomManagement from './screens/RoomManaagemnt/RoomManagement'; import RestaurantBarManagement from './screens/Restaurant&BarManagement/RestaurantBarManagement'; +import RestaurantAnalytics from './screens/RestaurantAnalytics'; +import { useContext } from 'react'; +import { AuthContext } from './components/context/AuthContext'; +import { Navigate } from 'react-router-dom'; +import { Toaster } from 'react-hot-toast'; const Layout = () => { return ( @@ -27,29 +26,28 @@ const Layout = () => { }; const App = () => { + const Redirector = () => { + const { user, loading } = useContext(AuthContext); + if (loading) return null; + return user ? : ; + }; return (
+ }> {/* Public Routes */} - } /> - } /> - {/* } /> - } /> - } /> - } /> - } /> - } /> */} - } /> - } /> - {/* } /> */} - } /> - {/* } /> */} - {/* Room Management Route */} - } /> - } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } />
diff --git a/Client/src/apiconfig.js b/Client/src/apiconfig.js new file mode 100644 index 0000000..b04981b --- /dev/null +++ b/Client/src/apiconfig.js @@ -0,0 +1 @@ +export const API_BASE_URL = "https://api.lushhotelcloud.com/api"; diff --git a/Client/src/components/Login.jsx b/Client/src/components/Login.jsx deleted file mode 100644 index 20da93b..0000000 --- a/Client/src/components/Login.jsx +++ /dev/null @@ -1,252 +0,0 @@ -import React, { useState, useEffect, useContext } from 'react'; -import { Eye, EyeOff, Mail, Lock, ArrowRight, Smartphone } from 'lucide-react'; -import { useNavigate, useLocation } from 'react-router-dom'; -import { AuthContext } from './context/AuthContext'; - -export default function Login() { - const navigate = useNavigate(); - const location = useLocation(); - const { api, login } = useContext(AuthContext); - const [formData, setFormData] = useState({ - email: '', - password: '', - rememberMe: false - }); - const [showPassword, setShowPassword] = useState(false); - const [errors, setErrors] = useState({}); - const [isLoading, setIsLoading] = useState(false); - const [isVisible, setIsVisible] = useState(false); - - useEffect(() => { - setIsVisible(true); - if (location.state?.message) { - setErrors({ form: location.state.message, isSuccess: true }); - } - }, [location.state]); - - const handleChange = (e) => { - const { name, value, type, checked } = e.target; - setFormData(prev => ({ - ...prev, - [name]: type === 'checkbox' ? checked : value - })); - - if (errors[name]) { - setErrors(prev => ({ - ...prev, - [name]: '' - })); - } - }; - - const validateForm = () => { - const newErrors = {}; - - if (!formData.email.trim()) { - newErrors.email = 'Email is required'; - } else if (!/\S+@\S+\.\S+/.test(formData.email)) { - newErrors.email = 'Please enter a valid email'; - } - - if (!formData.password.trim()) { - newErrors.password = 'Password is required'; - } else if (formData.password.length < 6) { - newErrors.password = 'Password must be at least 6 characters'; - } - - setErrors(prev => ({ ...prev, ...newErrors, form: '' })); - return Object.keys(newErrors).length === 0; - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - - if (!validateForm()) return; - - setIsLoading(true); - - try { - const response = await api.post('/api/users/login', { - email: formData.email, - password: formData.password - }); - - const { token, user } = response.data; - await login({ token, user, rememberMe: formData.rememberMe }); - localStorage.setItem('userMode', 'buyer'); // Set default mode to buyer - navigate('/', { replace: true }); - } catch (error) { - console.error('Login error:', error); - const errorMessage = error.response?.data?.message || 'Invalid credentials. Please try again.'; - setErrors({ form: errorMessage }); - } finally { - setIsLoading(false); - } - }; - - return ( -
-
-
-
-
- -
-

Welcome Back

-

Sign in to your DeviceMarket account

-

Your trusted marketplace for 2nd hand digital devices

-
- - {errors.form && ( -

- {errors.form} -

- )} - -
-
- -
- - e.target.style.borderColor = '#6ECC5A'} - onBlur={(e) => e.target.style.borderColor = formData.email && !errors.email ? '#6ECC5A' : '#cbd5e1'} - /> -
- {errors.email && ( -

{errors.email}

- )} -
- -
- -
- - e.target.style.borderColor = '#6ECC5A'} - onBlur={(e) => e.target.style.borderColor = formData.password && !errors.password ? '#6ECC5A' : '#cbd5e1'} - /> - -
- {errors.password && ( -

{errors.password}

- )} -
- -
- - -
- - -
- -
-

- Don't have an account?{' '} - -

-
-

Connect with buyers and sellers of:

-

Smartphones • Tablets • Laptops • Desktops • Components

-
-
-
-
- - -
- ); -} \ No newline at end of file diff --git a/Client/src/components/Navigation.jsx b/Client/src/components/Navigation.jsx index f232363..c3c4ad2 100644 --- a/Client/src/components/Navigation.jsx +++ b/Client/src/components/Navigation.jsx @@ -3,6 +3,7 @@ import { Link, useLocation, useNavigate } from 'react-router-dom'; import { Smartphone, Menu, X, User } from 'lucide-react'; import { AuthContext } from './context/AuthContext'; import axios from 'axios'; +import { API_BASE_URL } from '../apiconfig'; export default function Navigation() { const [isMenuOpen, setIsMenuOpen] = useState(false); @@ -63,7 +64,7 @@ export default function Navigation() { if (user) { try { // Get shop for this user by userId - const response = await axios.get(`http://localhost:8000/api/shops/by-user/${user._id}`); + const response = await axios.get(`${API_BASE_URL}/shops/by-user/${user._id}`); const shop = response.data; // Only redirect if shop exists if (shop && shop._id && shop.sellerId === shop._id) { diff --git a/Client/src/components/Register.jsx b/Client/src/components/Register.jsx deleted file mode 100644 index 4ce31ec..0000000 --- a/Client/src/components/Register.jsx +++ /dev/null @@ -1,471 +0,0 @@ -import React, { useState, useContext } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { Eye, EyeOff, Mail, Lock, User, ArrowRight, Phone, Smartphone, Tablet, Laptop, Monitor, Cpu, Shield, Users, Star } from 'lucide-react'; -import { AuthContext } from './context/AuthContext'; - -export default function Register() { - const { api } = useContext(AuthContext); - const [formData, setFormData] = useState({ - firstName: '', - lastName: '', - username: '', - email: '', - phone: '', - password: '', - confirmPassword: '', - acceptTerms: false, - acceptMarketing: false - }); - const [showPassword, setShowPassword] = useState(false); - const [showConfirmPassword, setShowConfirmPassword] = useState(false); - const [errors, setErrors] = useState({}); - const [isLoading, setIsLoading] = useState(false); - const navigate = useNavigate(); - - const handleChange = (e) => { - const { name, value, type, checked } = e.target; - setFormData(prev => ({ - ...prev, - [name]: type === 'checkbox' ? checked : value - })); - - if (errors[name]) { - setErrors(prev => ({ - ...prev, - [name]: '' - })); - } - }; - - const validateForm = () => { - const newErrors = {}; - - if (!formData.firstName.trim()) { - newErrors.firstName = 'First name is required'; - } - - if (!formData.lastName.trim()) { - newErrors.lastName = 'Last name is required'; - } - - if (!formData.username.trim()) { - newErrors.username = 'Username is required'; - } else if (formData.username.length < 3) { - newErrors.username = 'Username must be at least 3 characters'; - } - - if (!formData.email.trim()) { - newErrors.email = 'Email is required'; - } else if (!/\S+@\S+\.\S+/.test(formData.email)) { - newErrors.email = 'Please enter a valid email'; - } - - if (!formData.phone.trim()) { - newErrors.phone = 'Phone number is required'; - } else if (!/^[\+]?[1-9][\d]{0,15}$/.test(formData.phone.replace(/\s/g, ''))) { - newErrors.phone = 'Please enter a valid phone number'; - } - - if (!formData.password.trim()) { - newErrors.password = 'Password is required'; - } else if (formData.password.length < 8) { - newErrors.password = 'Password must be at least 8 characters'; - } else if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(formData.password)) { - newErrors.password = 'Password must contain uppercase, lowercase, and number'; - } - - if (!formData.confirmPassword.trim()) { - newErrors.confirmPassword = 'Please confirm your password'; - } else if (formData.password !== formData.confirmPassword) { - newErrors.confirmPassword = 'Passwords do not match'; - } - - if (!formData.acceptTerms) { - newErrors.acceptTerms = 'You must accept the terms and conditions'; - } - - setErrors(newErrors); - return Object.keys(newErrors).length === 0; - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - - if (!validateForm()) return; - - setIsLoading(true); - - try { - const userData = { - name: `${formData.firstName} ${formData.lastName}`, - username: formData.username, - email: formData.email, - phone: formData.phone, - password: formData.password, - isAdmin: false - }; - - const response = await api.post('/api/users/register', userData); - - console.log('Registration successful:', response.data); - navigate('/login', { state: { message: 'Registration successful! Please log in.' } }); - } catch (error) { - console.error('Registration error:', error); - const errorMessage = error.response?.data?.message || 'Registration failed. Please try again.'; - setErrors({ form: errorMessage }); - } finally { - setIsLoading(false); - } - }; - - return ( -
-
-
-
- {/* Left Column - Info Section */} -
-
-

- Welcome to the Digital Device Platform -

- -
-
Find or sell your device type:
-
-
- - Smartphones -
-
- - Tablets -
-
- - Laptops -
-
- - Desktops -
-
- - Components -
-
- - Accessories -
-
-
- -
-
- - Direct contact between buyers and sellers -
-
- - Wide range of digital devices -
-
- - Community-driven and easy to use -
-
- - Secure transactions and user verification -
-
-
-
- - {/* Right Column - Form Section */} -
-
-
-
-

Create Your Account

-

Join our community of buyers and sellers

-
- - {errors.form && ( -
-

{errors.form}

-
- )} - -
- {/* Name Fields Row */} -
-
- -
- - -
- {errors.firstName && ( -

{errors.firstName}

- )} -
- -
- -
- - -
- {errors.lastName && ( -

{errors.lastName}

- )} -
-
- - {/* Username Field */} -
- -
- - -
- {errors.username && ( -

{errors.username}

- )} -
- - {/* Email and Phone Row */} -
-
- -
- - -
- {errors.email && ( -

{errors.email}

- )} -
- -
- -
- - -
- {errors.phone && ( -

{errors.phone}

- )} -
-
- - {/* Password Fields Row */} -
-
- -
- - - -
- {errors.password && ( -

{errors.password}

- )} -
- -
- -
- - - -
- {errors.confirmPassword && ( -

{errors.confirmPassword}

- )} -
-
- - {/* Terms and Marketing Checkboxes */} -
-
- - -
- {errors.acceptTerms && ( -

{errors.acceptTerms}

- )} - -
- - -
-
- - {/* Submit Button */} - - - {/* Sign In Link */} -
-

- Already have an account?{' '} - -

-
-
-
-
-
-
-
-
-
- ); -} \ No newline at end of file diff --git a/Client/src/components/ScrollRestoration.jsx b/Client/src/components/ScrollRestoration.jsx index 0917642..6d90fd4 100644 --- a/Client/src/components/ScrollRestoration.jsx +++ b/Client/src/components/ScrollRestoration.jsx @@ -7,19 +7,33 @@ const ScrollRestoration = () => { useEffect(() => { // Scroll to top const scrollToTop = () => { - window.scrollTo({ - top: 0, - left: 0, - behavior: 'instant' - }); + console.log('[ScrollRestoration] scrollToTop called for pathname:', pathname); + try { + window.scrollTo({ + top: 0, + left: 0, + behavior: 'instant' + }); + } catch (err) { + window.scrollTo(0, 0); + } }; - scrollToTop(); - // Handle page refresh - window.addEventListener('load', scrollToTop); - + console.log('[ScrollRestoration] pathname changed ->', pathname); + if (window.__modalOpen) { + console.log('[ScrollRestoration] skipping scroll because modal is open'); + } else { + scrollToTop(); + } + + const onLoad = () => { + console.log('[ScrollRestoration] load event fired for', pathname); + scrollToTop(); + }; + window.addEventListener('load', onLoad); + return () => { - window.removeEventListener('load', scrollToTop); + window.removeEventListener('load', onLoad); }; }, [pathname]); diff --git a/Client/src/components/admin/InquiryManagement.jsx b/Client/src/components/admin/InquiryManagement.jsx index 6135a8a..e0e87e9 100644 --- a/Client/src/components/admin/InquiryManagement.jsx +++ b/Client/src/components/admin/InquiryManagement.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; import axios from 'axios'; import { X } from 'lucide-react'; +import { API_BASE_URL } from '../../apiconfig'; const ConfirmationModal = ({ isOpen, onClose, onConfirm, title, message }) => { if (!isOpen) return null; @@ -187,7 +188,7 @@ const InquiryManagement = () => { const fetchInquiries = async () => { try { - const response = await axios.get('/api/inquiries'); + const response = await axios.get(`${API_BASE_URL}/inquiries`); setInquiries(response.data); } catch (err) { console.error('Fetch inquiries error:', err); @@ -201,7 +202,7 @@ const InquiryManagement = () => { const confirmDelete = async () => { try { - await axios.delete(`/api/inquiries/${deleteModal.id}`); + await axios.delete(`${API_BASE_URL}/inquiries/${deleteModal.id}`); setInquiries(inquiries.filter((i) => i._id.$oid !== deleteModal.id)); setSuccess('Inquiry deleted successfully'); } catch (err) { @@ -218,7 +219,7 @@ const InquiryManagement = () => { const handleSendReply = async ({ inquiryId, subject, message }) => { try { - await axios.post('/api/inquiries/reply', { inquiryId, subject, message }); + await axios.post(`${API_BASE_URL}/inquiries/reply`, { inquiryId, subject, message }); setInquiries(inquiries.map((i) => i._id.$oid === inquiryId ? { ...i, replyMessage: message } : i )); diff --git a/Client/src/components/admin/UserManagement.jsx b/Client/src/components/admin/UserManagement.jsx index 6fe9d98..8310ff9 100644 --- a/Client/src/components/admin/UserManagement.jsx +++ b/Client/src/components/admin/UserManagement.jsx @@ -1,6 +1,8 @@ import React, { useState, useEffect, useContext } from 'react'; import { AuthContext } from '../context/AuthContext'; import { useNavigate } from 'react-router-dom'; +import { API_BASE_URL } from '../../apiconfig'; +import { toast } from 'react-hot-toast'; const UserManagement = () => { const { user, api } = useContext(AuthContext); @@ -10,6 +12,8 @@ const UserManagement = () => { const [success, setSuccess] = useState(''); const [loading, setLoading] = useState(true); const [confirmModal, setConfirmModal] = useState({ open: false, action: null, message: '', userId: null }); + const [createForm, setCreateForm] = useState({ name: '', email: '', password: '', role: 'guest', isAdmin: false }); + const [editModal, setEditModal] = useState({ open: false, user: null }); // Authentication check useEffect(() => { @@ -43,7 +47,7 @@ const UserManagement = () => { const fetchUsers = async () => { try { setLoading(true); - const response = await api.get('/api/users/all'); + const response = await api.get(`${API_BASE_URL}/users/all`); console.log('Fetched users:', response.data); setUsers(response.data); } catch (err) { @@ -67,7 +71,7 @@ const UserManagement = () => { const executeDeleteUser = async (id) => { try { console.log('Executing delete for user:', id); - await api.delete(`/api/users/${id}`); + await api.delete(`${API_BASE_URL}/users/${id}`); setUsers(users.filter(u => u._id !== id)); setSuccess('User deleted successfully'); } catch (err) { @@ -90,7 +94,7 @@ const UserManagement = () => { const executeToggleAdmin = async (id, currentStatus) => { try { console.log('Executing toggle admin for user:', id); - const response = await api.put(`/api/users/${id}/toggle-admin`, { + const response = await api.put(`${API_BASE_URL}/users/${id}/toggle-admin`, { isAdmin: !currentStatus }); setUsers(users.map(u => u._id === id ? { ...u, isAdmin: response.data.user.isAdmin } : u)); @@ -107,6 +111,52 @@ const UserManagement = () => { setConfirmModal({ open: false, action: null, message: '', userId: null }); }; + // Create user + const handleCreateUser = async (e) => { + e.preventDefault(); + try { + const body = { ...createForm }; + const res = await api.post(`${API_BASE_URL}/users`, body); + const newUser = res.data.user; + setUsers([...(users || []), newUser]); + setCreateForm({ name: '', email: '', password: '', role: 'guest', isAdmin: false }); + setSuccess('User created'); + toast.success('User created successfully'); + } catch (err) { + console.error('Create user error', err); + const msg = err.response?.data?.error || err.message || 'Failed to create user'; + setError(msg); + toast.error(msg); + } + }; + + // Edit user + const openEditModal = (user) => { + setEditModal({ open: true, user: { ...user } }); + }; + const closeEditModal = () => setEditModal({ open: false, user: null }); + + const handleEditSave = async () => { + try { + const u = editModal.user; + const payload = {}; + if (u.status !== undefined) payload.status = u.status; + if (u.isActive !== undefined) payload.isActive = u.isActive; + if (u.lastLogin !== undefined) payload.lastLogin = u.lastLogin; + const res = await api.patch(`${API_BASE_URL}/users/${u._id}`, payload); + const updated = res.data.user; + setUsers(users.map(x => x._id === updated._id ? updated : x)); + closeEditModal(); + setSuccess('User updated'); + toast.success('User updated successfully'); + } catch (err) { + console.error('Edit user error', err); + const msg = err.response?.data?.error || err.message || 'Failed to update user'; + setError(msg); + toast.error(msg); + } + }; + if (loading || !user || !user.isAdmin) { return (
@@ -123,6 +173,27 @@ const UserManagement = () => { {error &&
{error}
} {success &&
{success}
} +
+

Create User

+
+ setCreateForm({...createForm, name: e.target.value})} placeholder="Full name" className="p-3 border rounded-xl" /> + setCreateForm({...createForm, email: e.target.value})} placeholder="Email" type="email" className="p-3 border rounded-xl" /> + setCreateForm({...createForm, password: e.target.value})} placeholder="Password" type="password" className="p-3 border rounded-xl" /> +
+ + + +
+
+
+ {/* User List */}

Users

@@ -138,38 +209,53 @@ const UserManagement = () => { {users.length > 0 ? ( - users.map(u => ( - - {u.name} - {u.email} - {u.isAdmin ? 'Admin' : 'Not Admin'} - -
)} -
- - -
- -

{reservation.guestName}

-

Outstanding Balance: ${outstandingBalance.toFixed(2)}

-
- -
- - setPaymentData({ ...paymentData, amount: e.target.value })} - className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" - required - /> -
- -
- - -
- -
- - -
- -
- - setPaymentData({ ...paymentData, reference: e.target.value })} - className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" - placeholder="Transaction reference" - /> -
- -
- -