diff --git a/client/src/App.js b/client/src/App.js
index 188f649..cd1edef 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -33,7 +33,6 @@ import ContentManagement from './pages/admin/ContentManagement';
import RoomManagement from './pages/admin/RoomManagement';
import BookingManagement from './pages/admin/BookingManagement';
import MediaManagement from './pages/admin/MediaManagement';
-import BlogManagement from './pages/admin/BlogManagement';
import SettingsManagement from './pages/admin/SettingsManagement';
// Loading Component
@@ -93,18 +92,16 @@ function App() {
}
>
- {/* NEW: redirect /admin -> /admin/dashboard */}
} />
} />
} />
} />
} />
- } />
} />
} />
- {/* Optional fallback: keep if you want unknown admin routes to land on dashboard */}
+ {/* أي مسار غير معروف داخل /admin يروح للداشبورد */}
} />
diff --git a/client/src/components/admin/AdminLayout.js b/client/src/components/admin/AdminLayout.js
index 79aa9c8..4e77115 100644
--- a/client/src/components/admin/AdminLayout.js
+++ b/client/src/components/admin/AdminLayout.js
@@ -22,37 +22,28 @@ import {
import {
Menu as MenuIcon,
Dashboard as DashboardIcon,
- Article as ArticleIcon,
Hotel as HotelIcon,
CalendarMonth as CalendarIcon,
Settings as SettingsIcon,
Image as ImageIcon,
ContentPaste as ContentIcon,
- People as PeopleIcon,
Logout as LogoutIcon,
- AccountCircle as AccountIcon,
- Assessment as AssessmentIcon
+ AccountCircle as AccountIcon
} from '@mui/icons-material';
import { useAuth } from '../../context/AuthContext';
const drawerWidth = 260;
+// ✅ Removed: Blog, Guests, Analytics (UI only)
const menuItems = [
{ title: 'Dashboard', path: '/admin/dashboard', icon: },
{ title: 'Content', path: '/admin/content', icon: },
{ title: 'Rooms', path: '/admin/rooms', icon: },
{ title: 'Bookings', path: '/admin/bookings', icon: },
- { title: 'Blog', path: '/admin/blog', icon: },
{ title: 'Media', path: '/admin/media', icon: },
- { title: 'Guests', path: '/admin/guests', icon: },
- { title: 'Analytics', path: '/admin/analytics', icon: },
{ title: 'Settings', path: '/admin/settings', icon: },
];
-// ✅ hide from sidebar only (keep code/routes for future)
-const hiddenMenuTitles = new Set(['Guests', 'Blog']);
-const adminMenuItems = menuItems.filter(item => !hiddenMenuTitles.has(item.title));
-
const AdminLayout = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
@@ -82,9 +73,7 @@ const AdminLayout = () => {
const handleNavigate = (path) => {
navigate(path);
- if (isMobile) {
- setMobileOpen(false);
- }
+ if (isMobile) setMobileOpen(false);
};
const drawer = (
@@ -106,7 +95,7 @@ const AdminLayout = () => {
- {adminMenuItems.map((item) => (
+ {menuItems.map((item) => (
{
>
+
The Old Vine Hotel
@@ -175,14 +165,8 @@ const AdminLayout = () => {
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleProfileMenuClose}
- anchorOrigin={{
- vertical: 'bottom',
- horizontal: 'right',
- }}
- transformOrigin={{
- vertical: 'top',
- horizontal: 'right',
- }}
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
+ transformOrigin={{ vertical: 'top', horizontal: 'right' }}
>
))}
+ {/* ✅ بدل واتساب: يروح على صفحة /booking */}
{
+ {/* ✅ بدل واتساب: يروح على صفحة /booking */}
{
+ // يدعم كذا شكل للريسبونس
+ return res?.data?.data?.settings || res?.data?.data || res?.data || null;
+};
+
+export const SettingsProvider = ({ children }) => {
+ const [settings, setSettings] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ let mounted = true;
+
+ (async () => {
+ try {
+ // ✅ public endpoint
+ const res = await api.get('/api/settings/public');
+ if (mounted) setSettings(unwrapSettings(res));
+ } catch (e) {
+ console.error('Error loading public settings:', e);
+ } finally {
+ if (mounted) setLoading(false);
+ }
+ })();
+
+ return () => { mounted = false; };
+ }, []);
+
+ const value = useMemo(() => ({ settings, loading }), [settings, loading]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useSettings = () => useContext(SettingsContext);
diff --git a/client/src/pages/Booking.js b/client/src/pages/Booking.js
index 4a74586..7a240a8 100644
--- a/client/src/pages/Booking.js
+++ b/client/src/pages/Booking.js
@@ -1,11 +1,20 @@
import React, { useEffect, useState } from 'react';
import {
- Container, Typography, Box, TextField, Button,
- MenuItem, Alert, CircularProgress
+ Container,
+ Typography,
+ Box,
+ TextField,
+ Button,
+ MenuItem,
+ Alert,
+ CircularProgress
} from '@mui/material';
+import { useNavigate } from 'react-router-dom';
import api from '../utils/api';
const Booking = () => {
+ const navigate = useNavigate();
+
const [rooms, setRooms] = useState([]);
const [loadingRooms, setLoadingRooms] = useState(true);
const [submitting, setSubmitting] = useState(false);
@@ -25,35 +34,69 @@ const Booking = () => {
});
useEffect(() => {
+ let mounted = true;
+
const loadRooms = async () => {
try {
const res = await api.get('/api/rooms?limit=100');
- setRooms(res?.data?.data?.rooms || []);
+ const fetched = res?.data?.data?.rooms || [];
+ if (mounted) setRooms(fetched);
} catch (e) {
- setMessage({ type: 'error', text: 'Failed to load rooms' });
+ if (mounted) setMessage({ type: 'error', text: 'Failed to load rooms' });
} finally {
- setLoadingRooms(false);
+ if (mounted) setLoadingRooms(false);
}
};
+
loadRooms();
+
+ return () => { mounted = false; };
}, []);
const handleChange = (key) => (e) => {
setForm(prev => ({ ...prev, [key]: e.target.value }));
};
+ const validateBeforeSubmit = () => {
+ // check dates
+ if (form.checkInDate && form.checkOutDate) {
+ const inD = new Date(form.checkInDate);
+ const outD = new Date(form.checkOutDate);
+ if (inD >= outD) {
+ setMessage({ type: 'error', text: 'Check-out date must be after check-in date' });
+ return false;
+ }
+ }
+
+ const adults = Number(form.adults);
+ const children = Number(form.children || 0);
+ if (!Number.isFinite(adults) || adults < 1) {
+ setMessage({ type: 'error', text: 'Adults must be at least 1' });
+ return false;
+ }
+ if (!Number.isFinite(children) || children < 0) {
+ setMessage({ type: 'error', text: 'Children cannot be negative' });
+ return false;
+ }
+
+ return true;
+ };
+
const handleSubmit = async (e) => {
e.preventDefault();
- setSubmitting(true);
setMessage(null);
+ if (!validateBeforeSubmit()) return;
+
+ setSubmitting(true);
+
try {
const payload = {
guestInfo: {
- firstName: form.firstName,
- lastName: form.lastName,
- email: form.email,
- phone: form.phone
+ firstName: form.firstName.trim(),
+ lastName: form.lastName.trim(),
+ email: form.email.trim(),
+ phone: form.phone.trim()
},
roomId: form.roomId,
checkInDate: form.checkInDate,
@@ -65,24 +108,37 @@ const Booking = () => {
specialRequests: form.specialRequests
};
+ // ✅ هذا لازم يكون موجود بالـ CMS: POST /api/bookings/request
const res = await api.post('/api/bookings/request', payload);
- const bookingNumber = res?.data?.data?.booking?.bookingNumber;
- setMessage({
- type: 'success',
- text: bookingNumber
- ? `Booking request submitted! Booking #: ${bookingNumber}`
- : 'Booking request submitted successfully!'
- });
+ const booking = res?.data?.data?.booking || null;
+ const bookingNumber = booking?.bookingNumber || res?.data?.data?.bookingNumber || null;
+
+ const selectedRoom = rooms.find(r => r._id === form.roomId);
+
+ // ✅ معلومات نبعثها لصفحة BookingConfirmation
+ const requestForConfirmation = {
+ fullName: `${form.firstName} ${form.lastName}`.trim(),
+ phone: form.phone,
+ email: form.email,
+ checkInDate: form.checkInDate,
+ checkOutDate: form.checkOutDate,
+ adults: Number(form.adults),
+ children: Number(form.children || 0),
+ roomCategory: selectedRoom?.name || '',
+ message: form.specialRequests || '',
+ bookingNumber: bookingNumber || '',
+ autoOpenWhatsApp: true // إذا صفحة الـ Confirmation عندك بتفتح واتساب تلقائيًا
+ };
+
+ // ✅ روح على صفحة التأكيد (بدون ما نظل هون)
+ navigate('/booking/confirmation', { state: { request: requestForConfirmation } });
- // reset minimal
- setForm(prev => ({ ...prev, specialRequests: '' }));
} catch (error) {
setMessage({
type: 'error',
text: error.response?.data?.message || 'Failed to submit booking request'
});
- } finally {
setSubmitting(false);
}
};
@@ -109,7 +165,11 @@ const Booking = () => {
) : (
-
+
diff --git a/client/src/pages/BookingConfirmation.js b/client/src/pages/BookingConfirmation.js
index c83b2d3..c9cc181 100644
--- a/client/src/pages/BookingConfirmation.js
+++ b/client/src/pages/BookingConfirmation.js
@@ -1,30 +1,118 @@
-import React from 'react';
-import { Container, Typography, Box } from '@mui/material';
+import React, { useEffect, useMemo } from 'react';
+import { Container, Typography, Box, Button, Paper, Divider } from '@mui/material';
+import { useLocation, Link } from 'react-router-dom';
const BookingConfirmation = () => {
+ const location = useLocation();
+
+ // نتوقع إنه صفحة الحجز تبعتنا رح تعمل navigate وتبعت state فيه request
+ const request = location.state?.request || null;
+
+ const WA_NUMBER = useMemo(() => {
+ // إذا عندك رقم ثابت حاليا
+ return '963986105010';
+ }, []);
+
+ const waText = useMemo(() => {
+ if (!request) {
+ return encodeURIComponent('Hello, I would like to book a room at Old Vine Hotel.');
+ }
+
+ const lines = [
+ 'New booking request from website:',
+ `Name: ${request.fullName || ''}`,
+ `Phone: ${request.phone || ''}`,
+ request.email ? `Email: ${request.email}` : null,
+ request.checkInDate ? `Check-in: ${new Date(request.checkInDate).toLocaleDateString()}` : null,
+ request.checkOutDate ? `Check-out: ${new Date(request.checkOutDate).toLocaleDateString()}` : null,
+ request.adults != null ? `Adults: ${request.adults}` : null,
+ request.children != null ? `Children: ${request.children}` : null,
+ request.roomCategory ? `Category: ${request.roomCategory}` : null,
+ request.message ? `Message: ${request.message}` : null,
+ ].filter(Boolean);
+
+ return encodeURIComponent(lines.join('\n'));
+ }, [request]);
+
+ const WA_LINK = useMemo(() => `https://wa.me/${WA_NUMBER}?text=${waText}`, [WA_NUMBER, waText]);
+
+ // خيار: تفتح واتساب أوتوماتيك بعد نجاح الإرسال
+ useEffect(() => {
+ if (request?.autoOpenWhatsApp) {
+ window.open(WA_LINK, '_blank', 'noopener,noreferrer');
+ }
+ }, [request, WA_LINK]);
+
return (
-
+
- Booking Confirmation
+ Booking Request Sent
- Your reservation has been confirmed
+ We received your request ✅
-
-
- Thank you for your booking. You will receive a confirmation email shortly.
-
- {/* Add booking confirmation details here */}
+
+
+
+
+ Thank you! Our team will contact you soon. You can also confirm faster via WhatsApp.
+
+
+ {request && (
+ <>
+
+
+ Request Summary
+
+
+
+ Name: {request.fullName || '—'}
+ Phone: {request.phone || '—'}
+ {request.email && Email: {request.email}}
+ {request.roomCategory && Category: {request.roomCategory}}
+ {request.checkInDate && Check-in: {new Date(request.checkInDate).toLocaleDateString()}}
+ {request.checkOutDate && Check-out: {new Date(request.checkOutDate).toLocaleDateString()}}
+ {(request.adults != null || request.children != null) && (
+ Guests: {request.adults ?? 0} adults, {request.children ?? 0} children
+ )}
+ {request.message && Message: {request.message}}
+
+ >
+ )}
+
+
+
+ Open WhatsApp
+
+
+
+ Browse Rooms
+
+
+
+ Back Home
+
+
+
+ {!request && (
+
+ (No request details were passed to this page.)
+
+ )}
+
);
};
-export default BookingConfirmation;
\ No newline at end of file
+export default BookingConfirmation;
diff --git a/client/src/pages/admin/DashboardMain.js b/client/src/pages/admin/DashboardMain.js
index 5278c3f..a4a9705 100644
--- a/client/src/pages/admin/DashboardMain.js
+++ b/client/src/pages/admin/DashboardMain.js
@@ -13,8 +13,8 @@ import {
Hotel as HotelIcon,
CalendarMonth as CalendarIcon,
People as PeopleIcon,
- Article as ArticleIcon,
- TrendingUp as TrendingUpIcon
+ TrendingUp as TrendingUpIcon,
+ ContentPaste as ContentIcon
} from '@mui/icons-material';
import api from '../../utils/api';
import { useAuth } from '../../context/AuthContext';
@@ -73,9 +73,7 @@ const DashboardMain = () => {
const fetchStats = async () => {
try {
setLoading(true);
- console.log('📊 Dashboard: Fetching stats...');
const response = await api.get('/api/admin/stats');
- console.log('📊 Dashboard: Stats received:', response.data);
setStats(response.data.data.stats);
} catch (err) {
console.error('📊 Dashboard: Error fetching stats:', err);
@@ -93,9 +91,7 @@ const DashboardMain = () => {
);
}
- if (error) {
- return {error};
- }
+ if (error) return {error};
return (
@@ -109,7 +105,6 @@ const DashboardMain = () => {
- {/* Stats Cards */}
{
color="#7CBF9E"
/>
-
-
- }
- color="#3A635F"
- />
-
-
+ {/*
{
color="#0F2A26"
/>
+ */}
{/* Quick Actions */}
@@ -166,14 +152,12 @@ const DashboardMain = () => {
Quick Actions
+
-
+
window.location.href = '/admin/rooms'}
+ sx={{ cursor: 'pointer', '&:hover': { bgcolor: 'action.hover' } }}
+ onClick={() => (window.location.href = '/admin/rooms')}
>
@@ -182,13 +166,10 @@ const DashboardMain = () => {
-
+
window.location.href = '/admin/bookings'}
+ sx={{ cursor: 'pointer', '&:hover': { bgcolor: 'action.hover' } }}
+ onClick={() => (window.location.href = '/admin/bookings')}
>
@@ -197,31 +178,13 @@ const DashboardMain = () => {
-
+
window.location.href = '/admin/blog'}
+ sx={{ cursor: 'pointer', '&:hover': { bgcolor: 'action.hover' } }}
+ onClick={() => (window.location.href = '/admin/content')}
>
-
- Write Blog Post
-
-
-
-
-
- window.location.href = '/admin/content'}
- >
-
-
+
Edit Content
@@ -230,7 +193,6 @@ const DashboardMain = () => {
- {/* Recent Activity - Placeholder */}
@@ -249,4 +211,3 @@ const DashboardMain = () => {
};
export default DashboardMain;
-