diff --git a/client/build/images/logo.png b/client/build/images/logo.png index e198825..f720038 100644 Binary files a/client/build/images/logo.png and b/client/build/images/logo.png differ diff --git a/client/package-lock.json b/client/package-lock.json index 187cdcb..e9e17bc 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -30,6 +30,9 @@ "react-router-dom": "^6.19.0", "react-scripts": "^5.0.1", "swiper": "^11.0.5" + }, + "devDependencies": { + "cross-env": "^10.1.0" } }, "node_modules/@alloc/quick-lru": { @@ -2526,6 +2529,13 @@ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", "license": "MIT" }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "dev": true, + "license": "MIT" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", @@ -6565,6 +6575,24 @@ "node": ">=10" } }, + "node_modules/cross-env": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", + "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" + }, + "bin": { + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -16978,20 +17006,6 @@ } } }, - "node_modules/tailwindcss/node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/tapable": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", diff --git a/client/package.json b/client/package.json index b455171..ecb2805 100644 --- a/client/package.json +++ b/client/package.json @@ -27,7 +27,7 @@ "swiper": "^11.0.5" }, "scripts": { - "start": "PORT=3060 react-scripts start", + "start": "cross-env PORT=3060 react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" @@ -50,5 +50,8 @@ "last 1 safari version" ] }, - "proxy": "http://localhost:5080" + "proxy": "http://localhost:5080", + "devDependencies": { + "cross-env": "^10.1.0" + } } diff --git a/client/public/images/logo.png b/client/public/images/logo.png index e198825..f720038 100644 Binary files a/client/public/images/logo.png and b/client/public/images/logo.png differ diff --git a/client/src/components/layout/Header.js b/client/src/components/layout/Header.js index 393cee9..eb1ace4 100644 --- a/client/src/components/layout/Header.js +++ b/client/src/components/layout/Header.js @@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react'; import { AppBar, Toolbar, - Typography, Button, Box, IconButton, @@ -15,8 +14,6 @@ import { ListItemText, useMediaQuery, useTheme, - Select, - FormControl, } from '@mui/material'; import { Menu as MenuIcon, @@ -32,11 +29,21 @@ const Header = () => { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('lg')); const location = useLocation(); - + const [mobileOpen, setMobileOpen] = useState(false); const [languageAnchor, setLanguageAnchor] = useState(null); const [scrolled, setScrolled] = useState(false); + // ✅ لون غامق ثابت للهيدر الفاتح (حتى لو الثيم Dark) + const lightHeaderText = '#1a1a1a'; + + // ✅ Pending 17: Book Now -> WhatsApp with preset message + const WA_NUMBER = '963986105010'; + const WA_TEXT = encodeURIComponent( + 'For all booking inquiries and reservation confirmations, kindly contact us via WhatsApp' + ); + const WA_LINK = `https://wa.me/${WA_NUMBER}?text=${WA_TEXT}`; + const navigationItems = [ { label: t('nav.home'), path: '/' }, { label: t('nav.about'), path: '/about' }, @@ -53,25 +60,24 @@ const Header = () => { ]; useEffect(() => { - const handleScroll = () => { - setScrolled(window.scrollY > 50); - }; + const handleScroll = () => setScrolled(window.scrollY > 50); window.addEventListener('scroll', handleScroll); + handleScroll(); return () => window.removeEventListener('scroll', handleScroll); }, []); - const handleDrawerToggle = () => { - setMobileOpen(!mobileOpen); - }; + // ✅ صفحات الخلفية عندها فاتحة (زيدي هون أي route تاني) + const lightHeaderRoutes = ['/facilities', '/contact']; + const forceLightHeader = lightHeaderRoutes.some( + (r) => location.pathname === r || location.pathname.startsWith(r + '/') + ); - const handleLanguageClick = (event) => { - setLanguageAnchor(event.currentTarget); - }; + const isLightHeader = scrolled || forceLightHeader; - const handleLanguageClose = () => { - setLanguageAnchor(null); - }; + const handleDrawerToggle = () => setMobileOpen((prev) => !prev); + const handleLanguageClick = (event) => setLanguageAnchor(event.currentTarget); + const handleLanguageClose = () => setLanguageAnchor(null); const handleLanguageChange = (languageCode) => { i18n.changeLanguage(languageCode); handleLanguageClose(); @@ -100,7 +106,7 @@ const Header = () => { - + {navigationItems.map((item) => ( @@ -110,7 +116,7 @@ const Header = () => { sx={{ textAlign: 'center', backgroundColor: isActiveRoute(item.path) ? 'primary.main' : 'transparent', - color: isActiveRoute(item.path) ? 'white' : 'text.primary', + color: isActiveRoute(item.path) ? 'white' : lightHeaderText, '&:hover': { backgroundColor: 'primary.light', color: 'white', @@ -121,20 +127,21 @@ const Header = () => { ))} - + + {/* ✅ Pending 17: Drawer Book Now -> WhatsApp */} @@ -149,15 +156,22 @@ const Header = () => { - + {/* Logo */} { display: 'flex', alignItems: 'center', textDecoration: 'none', - '&:hover': { - opacity: 0.8, - }, + '&:hover': { opacity: 0.8 }, }} > Old Vine Hotel { component={Link} to={item.path} sx={{ - color: scrolled - ? (isActiveRoute(item.path) ? 'secondary.main' : 'text.primary') - : (isActiveRoute(item.path) ? 'white' : 'white'), + color: isLightHeader + ? (isActiveRoute(item.path) ? 'secondary.main' : lightHeaderText) + : 'white', fontWeight: isActiveRoute(item.path) ? 600 : 400, borderBottom: isActiveRoute(item.path) ? 2 : 0, borderColor: 'secondary.main', borderRadius: 0, - opacity: isActiveRoute(item.path) ? 1 : 0.8, + opacity: isActiveRoute(item.path) ? 1 : 0.85, '&:hover': { backgroundColor: 'transparent', - color: scrolled ? 'secondary.main' : 'white', + color: isLightHeader ? 'secondary.main' : 'white', opacity: 1, }, }} @@ -221,27 +233,26 @@ const Header = () => { ))} - + {/* Language Selector */} - - {/* Book Now Button */} + + {/* ✅ Pending 17: Book Now -> WhatsApp */} @@ -249,17 +260,13 @@ const Header = () => { )} - {/* Mobile Menu Button */} + {/* Mobile */} {isMobile && ( - + @@ -273,9 +280,7 @@ const Header = () => { anchor="right" open={mobileOpen} onClose={handleDrawerToggle} - ModalProps={{ - keepMounted: true, - }} + ModalProps={{ keepMounted: true }} sx={{ '& .MuiDrawer-paper': { boxSizing: 'border-box', @@ -307,9 +312,8 @@ const Header = () => { ))} - ); }; -export default Header; \ No newline at end of file +export default Header; diff --git a/client/src/index.css b/client/src/index.css index d276692..9f4870b 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -9,8 +9,8 @@ html { } body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + font-family: "Montserrat", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -18,6 +18,7 @@ body { line-height: 1.6; } + code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; diff --git a/client/src/locales/ar.json b/client/src/locales/ar.json index ca2c0bc..7238bb5 100644 --- a/client/src/locales/ar.json +++ b/client/src/locales/ar.json @@ -17,10 +17,12 @@ "exploreRooms": "استكشف الغرف" }, "home": { - "welcomeTitle": "مرحباً بكم في فندق العريشة القديمة", + "welcomeTitle": "مرحباً بكم في فندق الدالية القديمة", "welcomeSubtitle": "حيث تلتقي الرفاهية بالتقاليد", "welcomeDescription": "اكتشف تجربة ضيافة استثنائية في فندق العريشة القديمة، حيث كل تفصيل مصمم ليمنحك لحظات لا تُنسى من الراحة والأناقة.", "featuresTitle": "لماذا تختارنا", + "feature6Title": "قاعة اجتماعات", + "feature6Description": "يوفّر بيئة فريدة لرجال الأعمال لعقد اجتماعاتهم ومؤتمراتهم.", "feature1Title": "إقامة فاخرة", "feature1Description": "غرف وأجنحة أنيقة مع وسائل راحة متميزة", "feature2Title": "مطاعم راقية", @@ -97,6 +99,8 @@ "message": "الرسالة", "sendMessage": "إرسال رسالة", "messageSent": "تم إرسال الرسالة بنجاح!", + "whatsappOpened": "تم فتح واتساب. اضغط إرسال داخل واتساب لإرسال الرسالة.", + "requiredFields": "رجاءً املأ الحقول المطلوبة (الاسم، البريد، الرسالة).", "directions": "احصل على الاتجاهات", "whatsapp": "واتساب" }, diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 3d66c7c..7542c31 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -32,6 +32,8 @@ "roomsTitle": "Our Rooms & Suites", "roomsSubtitle": "Comfort and elegance in every detail", "viewAllRooms": "View All Rooms", + "feature6Title": "Meeting Room", + "feature6Description": "A unique setting for business meetings and conferences.", "offersTitle": "Special Offers", "offersSubtitle": "Exclusive deals for your perfect stay" }, diff --git a/client/src/locales/fr.json b/client/src/locales/fr.json index 38d9b0a..a97cec8 100644 --- a/client/src/locales/fr.json +++ b/client/src/locales/fr.json @@ -27,6 +27,8 @@ "feature2Description": "Cuisine exquise et expériences culinaires de classe mondiale", "feature4Title": "Emplacement privilégié", "feature5Title": "Belles terrasses", + "feature6Title": "Salle de réunion", + "feature6Description": "Un cadre unique pour les réunions d’affaires et les conférences.", "feature5Description": "Détendez-vous sur nos magnifiques terrasses avec des vues imprenables sur la vieille Damas", "feature4Description": "Parfaitement situé pour les affaires et les loisirs", "roomsTitle": "Nos chambres et suites", @@ -95,8 +97,11 @@ "hours": "Heures", "name": "Nom complet", "message": "Message", + "subject": "Sujet", "sendMessage": "Envoyer le message", "messageSent": "Message envoyé avec succès !", + "whatsappOpened": "WhatsApp est ouvert. Appuyez sur Envoyer dans WhatsApp pour envoyer votre message.", + "requiredFields": "Veuillez remplir les champs obligatoires (nom, e-mail, message).", "directions": "Obtenir l'itinéraire", "whatsapp": "WhatsApp" }, diff --git a/client/src/pages/About.js b/client/src/pages/About.js index 3f00d9a..28c688e 100644 --- a/client/src/pages/About.js +++ b/client/src/pages/About.js @@ -332,102 +332,104 @@ const About = () => { + {false && ( + + + + Our Journey + - {/* Timeline Section */} - - - + {/* Timeline line */} + + + {milestones.map((milestone, index) => ( + - Our Journey - - - - {/* Timeline line */} - - {milestones.map((milestone, index) => ( - - + + - - - - {milestone.year} - - - {milestone.title} - - - {milestone.description} - - - - - {/* Timeline dot */} - - - - - - ))} - - - + + {milestone.year} + + + {milestone.title} + + + {milestone.description} + + + + + {/* Timeline dot */} + + + + + + ))} + + + +)} ); diff --git a/client/src/pages/Contact.js b/client/src/pages/Contact.js index 7d02c67..eb3c025 100644 --- a/client/src/pages/Contact.js +++ b/client/src/pages/Contact.js @@ -5,18 +5,15 @@ import { Typography, Grid, Card, - CardContent, TextField, Button, IconButton, Alert, - useTheme, } from '@mui/material'; import { Phone, Email, LocationOn, - AccessTime, WhatsApp, Facebook, Instagram, @@ -26,132 +23,153 @@ import { import { useTranslation } from 'react-i18next'; import { motion } from 'framer-motion'; import { Helmet } from 'react-helmet-async'; -import { useQuery, useMutation } from 'react-query'; +import { useQuery } from 'react-query'; import axios from 'axios'; +const HOTEL_WHATSAPP = '963986105010'; + const Contact = () => { - const { t } = useTranslation(); - const theme = useTheme(); - + const { t, i18n } = useTranslation(); + + const lang = (i18n.resolvedLanguage || i18n.language || 'en').toLowerCase(); + const isAr = lang.startsWith('ar'); + const isFr = lang.startsWith('fr'); + + const tf = (key, en, ar, fr) => t(key, isAr ? ar : isFr ? fr : en); + const [formData, setFormData] = useState({ name: '', email: '', phone: '', subject: '', - message: '' + message: '', }); const [submitStatus, setSubmitStatus] = useState(null); - // Fetch contact information const { data: contactInfo } = useQuery( 'contactInfo', - () => axios.get('/api/contact/info').then(res => res.data.data), + () => axios.get('/api/contact/info').then((res) => res.data.data), { - staleTime: 5 * 60 * 1000, // 5 minutes - } - ); - - // Submit contact form - const submitContactForm = useMutation( - (formData) => axios.post('/api/contact', formData), - { - onSuccess: () => { - setSubmitStatus({ type: 'success', message: t('contact.messageSent') }); - setFormData({ name: '', email: '', phone: '', subject: '', message: '' }); - }, - onError: (error) => { - setSubmitStatus({ - type: 'error', - message: error.response?.data?.message || 'Failed to send message' - }); - } + staleTime: 5 * 60 * 1000, + retry: 0, } ); const handleInputChange = (e) => { - setFormData({ - ...formData, - [e.target.name]: e.target.value - }); + setFormData((prev) => ({ + ...prev, + [e.target.name]: e.target.value, + })); + }; + + const openWhatsApp = () => { + const lines = [ + tf( + 'contact.whatsappNewMessageHeader', + 'New message from website:', + 'رسالة جديدة من موقع الفندق:', + 'Nouveau message du site web :' + ), + `${tf('contact.whatsappLabelName', 'Name', 'الاسم', 'Nom')}: ${formData.name}`, + `${tf('contact.whatsappLabelEmail', 'Email', 'البريد الإلكتروني', 'E-mail')}: ${formData.email}`, + `${tf('contact.whatsappLabelPhone', 'Phone', 'الهاتف', 'Téléphone')}: ${formData.phone || '-'}`, + `${tf('contact.whatsappLabelSubject', 'Subject', 'الموضوع', 'Sujet')}: ${formData.subject || '-'}`, + `${tf('contact.whatsappLabelMessage', 'Message', 'الرسالة', 'Message')}: ${formData.message}`, + ]; + + const text = encodeURIComponent(lines.join('\n')); + const url = `https://wa.me/${HOTEL_WHATSAPP}?text=${text}`; + window.open(url, '_blank', 'noopener,noreferrer'); }; const handleSubmit = (e) => { e.preventDefault(); - if (formData.name && formData.email && formData.message) { - submitContactForm.mutate(formData); + + if (!formData.name || !formData.email || !formData.message) { + setSubmitStatus({ + type: 'error', + message: tf( + 'contact.requiredFields', + 'Please fill the required fields (name, email, message).', + 'يرجى تعبئة الحقول المطلوبة (الاسم، البريد الإلكتروني، الرسالة).', + 'Veuillez remplir les champs obligatoires (nom, e-mail, message).' + ), + }); + return; } + + openWhatsApp(); + + setSubmitStatus({ + type: 'success', + message: tf( + 'contact.whatsappOpened', + 'WhatsApp opened. Press Send inside WhatsApp to deliver your message.', + 'تم فتح واتساب. اضغط "إرسال" داخل واتساب لإرسال رسالتك.', + 'WhatsApp s’est ouvert. Appuyez sur Envoyer داخل WhatsApp pour إرسال رسالتك.' + ), + }); + + setFormData({ name: '', email: '', phone: '', subject: '', message: '' }); }; const contactCards = [ { - title: 'Phone', + title: tf('contact.cardPhoneTitle', 'Phone', 'الهاتف', 'Téléphone'), icon: , primary: '0112241609', - secondary: '24/7 Available', - action: 'tel:0112241609' + secondary: tf('contact.cardPhoneSecondary', '24/7 Available', 'متاح 24/7', 'Disponible 24/7'), + action: 'tel:0112241609', }, { - title: 'Email', + title: tf('contact.cardEmailTitle', 'Email', 'البريد الإلكتروني', 'E-mail'), icon: , primary: 'reservations@oldvinehotel.com', - secondary: 'Response within 24 hours', - action: 'mailto:reservations@oldvinehotel.com' + secondary: tf( + 'contact.cardEmailSecondary', + 'Response within 24 hours', + 'الرد خلال 24 ساعة', + 'Réponse sous 24 heures' + ), + action: 'mailto:reservations@oldvinehotel.com', }, { - title: 'WhatsApp', + title: tf('contact.cardWhatsAppTitle', 'WhatsApp', 'واتساب', 'WhatsApp'), icon: , primary: '+963986105010', - secondary: 'Quick responses', - action: 'https://wa.me/963986105010' + secondary: tf('contact.cardWhatsAppSecondary', 'Quick responses', 'رد سريع', 'Réponses rapides'), + action: `https://wa.me/${HOTEL_WHATSAPP}`, }, { - title: 'Address', + title: tf('contact.cardAddressTitle', 'Address', 'العنوان', 'Adresse'), icon: , - primary: contactInfo?.hotel?.address?.formatted || 'Old Damascus City', + primary: contactInfo?.hotel?.address?.formatted || tf('contact.addressFallback', 'Old Vine Hotel, Damascus', 'فندق أولد فاين - دمشق', 'Old Vine Hotel, Damas'), secondary: t('contact.directions'), - action: 'https://maps.google.com/?q=' + encodeURIComponent(contactInfo?.hotel?.address?.formatted || 'Old Damascus City') - } - ]; - - const departments = [ - { - name: 'Reservations', - phone: contactInfo?.departments?.reservations?.phone || '+1 (555) 123-4567', - email: contactInfo?.departments?.reservations?.email || 'reservations@oldvinehotel.com', - hours: contactInfo?.departments?.reservations?.hours || '24/7' + action: 'https://www.google.com/maps/search/?api=1&query=Old%20Vine%20Hotel%20Damascus', }, - { - name: 'Concierge', - phone: contactInfo?.departments?.concierge?.phone || '+1 (555) 123-4568', - email: contactInfo?.departments?.concierge?.email || 'concierge@oldvinehotel.com', - hours: contactInfo?.departments?.concierge?.hours || '6:00 AM - 12:00 AM' - }, - { - name: 'Restaurant', - phone: contactInfo?.departments?.restaurant?.phone || '+1 (555) 123-4569', - email: contactInfo?.departments?.restaurant?.email || 'restaurant@oldvinehotel.com', - hours: contactInfo?.departments?.restaurant?.hours || '6:30 AM - 11:00 PM' - }, - { - name: 'Spa & Wellness', - phone: contactInfo?.departments?.spa?.phone || '+1 (555) 123-4570', - email: contactInfo?.departments?.spa?.email || 'spa@oldvinehotel.com', - hours: contactInfo?.departments?.spa?.hours || '8:00 AM - 9:00 PM' - } ]; return ( <> - Contact Us - The Old Vine Hotel - + {tf('contact.pageTitle', 'Contact Us - The Old Vine Hotel', 'تواصل معنا - فندق أولد فاين', 'Contact - The Old Vine Hotel')} + {/* Hero Section */} { component="h1" sx={{ mb: 3, - fontFamily: 'Playfair Display', fontSize: { xs: '2.5rem', md: '3.5rem' }, }} > @@ -223,17 +240,10 @@ const Contact = () => { }, }} > - + {card.icon} - + {card.title} @@ -248,9 +258,8 @@ const Contact = () => { ))} - {/* Contact Form and Departments */} + {/* Contact Form */} - {/* Contact Form */} { viewport={{ once: true }} > - - Send us a Message + + {tf('contact.sendUsMessageTitle', 'Send us a Message', 'أرسل لنا رسالة', 'Envoyez-nous un message')} - + {submitStatus && ( - setSubmitStatus(null)} > @@ -315,7 +317,7 @@ const Contact = () => { { onChange={handleInputChange} /> + @@ -352,7 +354,7 @@ const Contact = () => { - {/* Social Media Section */} + {/* Social Media */} { transition={{ duration: 0.8 }} viewport={{ once: true }} > - {/* Social Media */} - - Follow Us + + {tf('contact.followUs', 'Follow Us', 'تابعنا', 'Suivez-nous')} - + @@ -392,11 +382,7 @@ const Contact = () => { href={contactInfo?.socialMedia?.instagram || '#'} target="_blank" rel="noopener noreferrer" - sx={{ - backgroundColor: '#E4405F', - color: 'white', - '&:hover': { backgroundColor: '#D62D4A' }, - }} + sx={{ backgroundColor: '#E4405F', color: 'white' }} > @@ -405,24 +391,16 @@ const Contact = () => { href={contactInfo?.socialMedia?.twitter || '#'} target="_blank" rel="noopener noreferrer" - sx={{ - backgroundColor: '#1DA1F2', - color: 'white', - '&:hover': { backgroundColor: '#1A91DA' }, - }} + sx={{ backgroundColor: '#1DA1F2', color: 'white' }} > @@ -436,18 +414,18 @@ const Contact = () => { {/* Map Section */}