final updates

This commit is contained in:
yotakii
2026-01-05 11:35:54 +03:00
parent 4d883c21c9
commit 54ce6ed114
12 changed files with 447 additions and 508 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -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",

View File

@@ -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"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -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,
@@ -37,6 +34,16 @@ const Header = () => {
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();
@@ -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',
@@ -122,19 +128,20 @@ const Header = () => {
</ListItem>
))}
{/* ✅ Pending 17: Drawer Book Now -> WhatsApp */}
<ListItem disablePadding>
<ListItemButton
component={Link}
to="/booking"
component="a"
href={WA_LINK}
target="_blank"
rel="noopener noreferrer"
sx={{
textAlign: 'center',
backgroundColor: 'secondary.main',
color: 'secondary.contrastText',
m: 2,
borderRadius: 2,
'&:hover': {
backgroundColor: 'secondary.dark',
},
'&:hover': { backgroundColor: 'secondary.dark' },
}}
>
<ListItemText primary={t('nav.booking')} />
@@ -149,15 +156,22 @@ const Header = () => {
<AppBar
position="fixed"
sx={{
backgroundColor: scrolled ? 'rgba(255, 255, 255, 0.95)' : 'transparent',
color: scrolled ? 'text.primary' : 'white',
backgroundColor: isLightHeader ? 'rgba(255, 255, 255, 0.95)' : 'transparent',
color: isLightHeader ? lightHeaderText : 'white',
transition: 'all 0.3s ease-in-out',
backdropFilter: scrolled ? 'blur(10px)' : 'none',
boxShadow: scrolled ? 3 : 0,
zIndex: 1300, // Ensure header is always on top
backdropFilter: isLightHeader ? 'blur(10px)' : 'none',
boxShadow: isLightHeader ? 3 : 0,
zIndex: 1300,
}}
>
<Toolbar sx={{ justifyContent: 'space-between', maxWidth: 1200, width: '100%', mx: 'auto' }}>
<Toolbar
sx={{
justifyContent: 'space-between',
maxWidth: 1200,
width: '100%',
mx: 'auto',
}}
>
{/* Logo */}
<motion.div
initial={{ opacity: 0, x: -20 }}
@@ -171,16 +185,14 @@ const Header = () => {
display: 'flex',
alignItems: 'center',
textDecoration: 'none',
'&:hover': {
opacity: 0.8,
},
'&:hover': { opacity: 0.8 },
}}
>
<img
src="/images/logo.png"
alt="Old Vine Hotel"
style={{
height: scrolled ? '50px' : '60px',
height: isLightHeader ? '50px' : '60px',
width: 'auto',
transition: 'all 0.3s ease-in-out',
}}
@@ -202,17 +214,17 @@ const Header = () => {
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,
},
}}
@@ -227,21 +239,20 @@ const Header = () => {
<LanguageIcon />
</IconButton>
{/* Book Now Button */}
{/* ✅ Pending 17: Book Now -> WhatsApp */}
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5, delay: 0.3 }}
>
<Button
component={Link}
to="/booking"
component="a"
href={WA_LINK}
target="_blank"
rel="noopener noreferrer"
variant="contained"
color="secondary"
sx={{
fontWeight: 600,
px: 3,
}}
sx={{ fontWeight: 600, px: 3 }}
>
{t('nav.booking')}
</Button>
@@ -249,17 +260,13 @@ const Header = () => {
</Box>
)}
{/* Mobile Menu Button */}
{/* Mobile */}
{isMobile && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<IconButton onClick={handleLanguageClick} sx={{ color: 'inherit' }}>
<LanguageIcon />
</IconButton>
<IconButton
color="inherit"
onClick={handleDrawerToggle}
sx={{ ml: 1 }}
>
<IconButton color="inherit" onClick={handleDrawerToggle} sx={{ ml: 1 }}>
<MenuIcon />
</IconButton>
</Box>
@@ -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,7 +312,6 @@ const Header = () => {
</MenuItem>
))}
</Menu>
</>
);
};

View File

@@ -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;

View File

@@ -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": "واتساب"
},

View File

@@ -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"
},

View File

@@ -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 daffaires 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"
},

View File

@@ -332,102 +332,104 @@ const About = () => {
</motion.div>
</Container>
</Box>
{false && (
<Container maxWidth="lg" sx={{ py: 8 }}>
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
viewport={{ once: true }}
>
<Typography
variant="h3"
component="h2"
textAlign="center"
sx={{ mb: 6, color: 'primary.main' }}
>
Our Journey
</Typography>
{/* Timeline Section */}
<Container maxWidth="lg" sx={{ py: 8 }}>
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
viewport={{ once: true }}
>
<Typography
variant="h3"
component="h2"
textAlign="center"
sx={{
mb: 6,
color: 'primary.main',
}}
<Box sx={{ position: 'relative' }}>
{/* Timeline line */}
<Box
sx={{
position: 'absolute',
left: '50%',
top: 0,
bottom: 0,
width: 2,
backgroundColor: 'primary.main',
transform: 'translateX(-50%)',
display: { xs: 'none', md: 'block' },
}}
/>
{milestones.map((milestone, index) => (
<motion.div
key={index}
initial={{ opacity: 0, x: index % 2 === 0 ? -50 : 50 }}
whileInView={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6, delay: index * 0.2 }}
viewport={{ once: true }}
>
Our Journey
</Typography>
<Box sx={{ position: 'relative' }}>
{/* Timeline line */}
<Box
sx={{
position: 'absolute',
left: '50%',
top: 0,
bottom: 0,
width: 2,
backgroundColor: 'primary.main',
transform: 'translateX(-50%)',
display: { xs: 'none', md: 'block' }
display: 'flex',
alignItems: 'center',
mb: 6,
flexDirection: {
xs: 'column',
md: index % 2 === 0 ? 'row' : 'row-reverse',
},
textAlign: { xs: 'center', md: 'left' },
}}
/>
{milestones.map((milestone, index) => (
<motion.div
key={index}
initial={{ opacity: 0, x: index % 2 === 0 ? -50 : 50 }}
whileInView={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6, delay: index * 0.2 }}
viewport={{ once: true }}
>
<Box
>
<Box sx={{ flex: 1, px: { xs: 0, md: 4 } }}>
<Card
sx={{
display: 'flex',
alignItems: 'center',
mb: 6,
flexDirection: { xs: 'column', md: index % 2 === 0 ? 'row' : 'row-reverse' },
textAlign: { xs: 'center', md: 'left' }
p: 3,
maxWidth: 400,
mx: { xs: 'auto', md: index % 2 === 0 ? 0 : 'auto' },
}}
>
<Box sx={{ flex: 1, px: { xs: 0, md: 4 } }}>
<Card sx={{ p: 3, maxWidth: 400, mx: { xs: 'auto', md: index % 2 === 0 ? 0 : 'auto' } }}>
<Typography
variant="h4"
component="h3"
sx={{
color: 'secondary.main',
fontWeight: 700,
mb: 1
}}
>
{milestone.year}
</Typography>
<Typography variant="h6" sx={{ mb: 2, fontWeight: 600 }}>
{milestone.title}
</Typography>
<Typography variant="body2" color="text.secondary">
{milestone.description}
</Typography>
</Card>
</Box>
<Typography
variant="h4"
component="h3"
sx={{ color: 'secondary.main', fontWeight: 700, mb: 1 }}
>
{milestone.year}
</Typography>
<Typography variant="h6" sx={{ mb: 2, fontWeight: 600 }}>
{milestone.title}
</Typography>
<Typography variant="body2" color="text.secondary">
{milestone.description}
</Typography>
</Card>
</Box>
{/* Timeline dot */}
<Box
sx={{
width: 20,
height: 20,
backgroundColor: 'secondary.main',
borderRadius: '50%',
border: `4px solid ${theme.palette.background.paper}`,
boxShadow: 2,
display: { xs: 'none', md: 'block' },
zIndex: 1
}}
/>
{/* Timeline dot */}
<Box
sx={{
width: 20,
height: 20,
backgroundColor: 'secondary.main',
borderRadius: '50%',
border: `4px solid ${theme.palette.background.paper}`,
boxShadow: 2,
display: { xs: 'none', md: 'block' },
zIndex: 1,
}}
/>
<Box sx={{ flex: 1 }} />
</Box>
</motion.div>
))}
</Box>
</motion.div>
</Container>
<Box sx={{ flex: 1 }} />
</Box>
</motion.div>
))}
</Box>
</motion.div>
</Container>
)}
</>
);

View File

@@ -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 sest 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: <Phone sx={{ fontSize: 40 }} />,
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: <Email sx={{ fontSize: 40 }} />,
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: <WhatsApp sx={{ fontSize: 40 }} />,
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: <LocationOn sx={{ fontSize: 40 }} />,
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 (
<>
<Helmet>
<title>Contact Us - The Old Vine Hotel</title>
<meta name="description" content="Get in touch with The Old Vine Hotel. Contact us for reservations, inquiries, or any assistance you need during your stay." />
<title>{tf('contact.pageTitle', 'Contact Us - The Old Vine Hotel', 'تواصل معنا - فندق أولد فاين', 'Contact - The Old Vine Hotel')}</title>
<meta
name="description"
content={tf(
'contact.pageDescription',
'Get in touch with The Old Vine Hotel. Contact us for reservations, inquiries, or any assistance you need during your stay.',
'تواصل مع فندق أولد فاين للحجوزات والاستفسارات وأي مساعدة تحتاجها أثناء إقامتك.',
'Contactez The Old Vine Hotel pour les réservations, questions, ou toute assistance pendant votre séjour.'
)}
/>
</Helmet>
{/* Hero Section */}
<Box
sx={{
minHeight: '60vh',
background: 'linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)), url("/images/gallery/hotel-gallery/31.jpg") center/cover',
background:
'linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)), url("/images/gallery/hotel-gallery/31.jpg") center/cover',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
@@ -170,7 +188,6 @@ const Contact = () => {
component="h1"
sx={{
mb: 3,
fontFamily: 'Playfair Display',
fontSize: { xs: '2.5rem', md: '3.5rem' },
}}
>
@@ -223,17 +240,10 @@ const Contact = () => {
},
}}
>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
mb: 2,
color: 'primary.main',
}}
>
<Box sx={{ display: 'flex', justifyContent: 'center', mb: 2, color: 'primary.main' }}>
{card.icon}
</Box>
<Typography variant="h6" component="h3" sx={{ mb: 2, fontWeight: 600 }}>
<Typography variant="h6" sx={{ mb: 2, fontWeight: 600 }}>
{card.title}
</Typography>
<Typography variant="body1" sx={{ mb: 1, fontWeight: 500 }}>
@@ -248,9 +258,8 @@ const Contact = () => {
))}
</Grid>
{/* Contact Form and Departments */}
{/* Contact Form */}
<Grid container spacing={6}>
{/* Contact Form */}
<Grid item xs={12} md={12}>
<motion.div
initial={{ opacity: 0, x: -30 }}
@@ -259,15 +268,8 @@ const Contact = () => {
viewport={{ once: true }}
>
<Card sx={{ p: 4 }}>
<Typography
variant="h4"
component="h2"
sx={{
mb: 3,
color: 'primary.main',
}}
>
Send us a Message
<Typography variant="h4" sx={{ mb: 3, color: 'primary.main' }}>
{tf('contact.sendUsMessageTitle', 'Send us a Message', 'أرسل لنا رسالة', 'Envoyez-nous un message')}
</Typography>
{submitStatus && (
@@ -315,7 +317,7 @@ const Contact = () => {
<Grid item xs={12} sm={6}>
<TextField
name="subject"
label="Subject"
label={tf('contact.subject', 'Subject', 'الموضوع', 'Sujet')}
fullWidth
value={formData.subject}
onChange={handleInputChange}
@@ -333,6 +335,7 @@ const Contact = () => {
onChange={handleInputChange}
/>
</Grid>
<Grid item xs={12}>
<Button
type="submit"
@@ -340,10 +343,9 @@ const Contact = () => {
color="primary"
size="large"
startIcon={<Send />}
disabled={submitContactForm.isLoading}
sx={{ px: 4, py: 2 }}
>
{submitContactForm.isLoading ? 'Sending...' : t('contact.sendMessage')}
{t('contact.sendMessage')}
</Button>
</Grid>
</Grid>
@@ -352,7 +354,7 @@ const Contact = () => {
</motion.div>
</Grid>
{/* Social Media Section */}
{/* Social Media */}
<Grid item xs={12} md={5}>
<motion.div
initial={{ opacity: 0, x: 30 }}
@@ -360,17 +362,9 @@ const Contact = () => {
transition={{ duration: 0.8 }}
viewport={{ once: true }}
>
{/* Social Media */}
<Card sx={{ p: 4 }}>
<Typography
variant="h5"
component="h3"
sx={{
mb: 3,
color: 'primary.main',
}}
>
Follow Us
<Typography variant="h5" sx={{ mb: 3, color: 'primary.main' }}>
{tf('contact.followUs', 'Follow Us', 'تابعنا', 'Suivez-nous')}
</Typography>
<Box sx={{ display: 'flex', gap: 2 }}>
@@ -379,11 +373,7 @@ const Contact = () => {
href={contactInfo?.socialMedia?.facebook || '#'}
target="_blank"
rel="noopener noreferrer"
sx={{
backgroundColor: '#1877F2',
color: 'white',
'&:hover': { backgroundColor: '#166FE5' },
}}
sx={{ backgroundColor: '#1877F2', color: 'white' }}
>
<Facebook />
</IconButton>
@@ -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' }}
>
<Instagram />
</IconButton>
@@ -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' }}
>
<Twitter />
</IconButton>
<IconButton
component="a"
href="https://wa.me/963986105010"
href={`https://wa.me/${HOTEL_WHATSAPP}`}
target="_blank"
rel="noopener noreferrer"
sx={{
backgroundColor: '#25D366',
color: 'white',
'&:hover': { backgroundColor: '#22C55E' },
}}
sx={{ backgroundColor: '#25D366', color: 'white' }}
>
<WhatsApp />
</IconButton>
@@ -436,14 +414,14 @@ const Contact = () => {
{/* Map Section */}
<Box sx={{ width: '100%', height: 400, bgcolor: 'grey.200' }}>
<iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3310.1234567890123!2d36.2765!3d33.5138!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x1518e6dc413cc6a7%3A0x6b9f3c2b8b8b8b8b!2sOld%20Damascus%20City!5e0!3m2!1sen!2ssy!4v1704067200000!5m2!1sen!2ssy&zoom=16"
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d881.089256494776!2d36.30748342144017!3d33.51308026272531!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x1518e728348b96bf%3A0x9a06409382325607!2sOld%20Vine%20Hotel!5e0!3m2!1$ar!2s!4v1766475597734!5m2!1$ar!2s"
width="100%"
height="100%"
style={{ border: 0 }}
allowFullScreen=""
allowFullScreen
loading="lazy"
referrerPolicy="no-referrer-when-downgrade"
title="Hotel Location"
title={tf('contact.mapTitle', 'Hotel Location', 'موقع الفندق', 'Emplacement de lhôtel')}
/>
</Box>
</>

View File

@@ -9,20 +9,14 @@ import {
CardContent,
CardMedia,
Chip,
useTheme,
CircularProgress,
} from '@mui/material';
import {
Hotel,
Restaurant,
FitnessCenter,
LocationOn,
Star,
CheckCircle,
Spa,
Pool,
BusinessCenter,
Deck,
BusinessCenter,
} from '@mui/icons-material';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
@@ -32,22 +26,19 @@ import staticData from '../utils/staticData';
const Home = () => {
const { t, i18n } = useTranslation();
const theme = useTheme();
const [content, setContent] = useState(null);
const [roomCategories, setRoomCategories] = useState([]);
const [loading, setLoading] = useState(true);
const currentLanguage = i18n.language;
// Fetch homepage content and room categories from static data
useEffect(() => {
const fetchContent = async () => {
try {
const [homeContent, categories] = await Promise.all([
staticData.getHomeContent(),
staticData.getRoomCategories()
staticData.getRoomCategories(),
]);
setContent(homeContent);
// Get first 3 categories
setRoomCategories(categories.slice(0, 3));
} catch (error) {
console.error('Error loading homepage content:', error);
@@ -80,29 +71,41 @@ const Home = () => {
title: t('home.feature5Title'),
description: t('home.feature5Description'),
},
{
icon: <BusinessCenter sx={{ fontSize: 40 }} />,
title: t('home.feature6Title', 'Meeting Room'),
description: t(
'home.feature6Description',
'A unique business-friendly setting for meetings and conferences.'
),
},
];
// Map room categories from API to display format
const heroImage = content?.hero?.backgroundImage || '/images/hero.jpg';
const welcomeSection = content?.sections?.find((s) => s.sectionId === 'welcome') || {};
const useTranslations = currentLanguage !== 'en';
const welcomeTitle = useTranslations
? t('home.welcomeTitle')
: (welcomeSection.title || t('home.welcomeTitle'));
const welcomeSubtitle = useTranslations
? t('home.welcomeSubtitle')
: (welcomeSection.subtitle || t('home.welcomeSubtitle'));
const welcomeContent = useTranslations
? t('home.welcomeDescription')
: (welcomeSection.content || t('home.welcomeDescription'));
const roomTypes = roomCategories.map((category) => ({
id: category._id || category.slug,
name: category.name,
image: category.primaryImage || '/images/room-default.jpg',
price: category.priceRange?.min || 0,
features: category.features?.slice(0, 4) || [], // Show first 4 features
features: category.features?.slice(0, 4) || [],
slug: category.slug,
}));
// Special offers - using translations
const specialOffers = [
{
title: t('home.offersTitle'),
description: t('home.offersSubtitle'),
discount: '25% OFF',
validUntil: '2025-12-31',
},
];
// Show loading state
if (loading) {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh' }}>
@@ -111,98 +114,59 @@ const Home = () => {
);
}
// Use translations for non-English languages, static data for English
const useTranslations = currentLanguage !== 'en';
// Fallback content - prioritize translations for non-English
const heroTitle = useTranslations ? t('hero.hotelName') : (content?.hero?.title || t('hero.hotelName'));
const heroSubtitle = useTranslations ? t('hero.subtitle') : (content?.hero?.subtitle || t('hero.subtitle'));
const heroDescription = useTranslations ? '' : (content?.hero?.description || '');
const heroImage = content?.hero?.backgroundImage || '/images/hero.jpg';
const heroCTAText = useTranslations ? t('hero.exploreRooms') : (content?.hero?.ctaText || t('hero.exploreRooms'));
const heroCTALink = content?.hero?.ctaLink || '/rooms';
// Get welcome section from content
const welcomeSection = content?.sections?.find(s => s.sectionId === 'welcome') || {};
const welcomeTitle = useTranslations ? t('home.welcomeTitle') : (welcomeSection.title || t('home.welcomeTitle'));
const welcomeSubtitle = useTranslations ? t('home.welcomeSubtitle') : (welcomeSection.subtitle || t('home.welcomeSubtitle'));
const welcomeContent = useTranslations ? t('home.welcomeDescription') : (welcomeSection.content || t('home.welcomeDescription'));
const LOGO_BOTTOM = { xs: 40, sm: 55, md: 70 };
return (
<>
<Helmet>
<title>{content?.seo?.title || 'The Old Vine Hotel - Luxury Accommodation & Premium Hospitality'}</title>
<meta name="description" content={content?.seo?.description || 'Experience luxury and elegance at The Old Vine Hotel.'} />
<meta name="keywords" content={content?.seo?.keywords?.join(', ') || 'luxury hotel, premium accommodation'} />
<meta
name="description"
content={content?.seo?.description || 'Experience luxury and elegance at The Old Vine Hotel.'}
/>
<meta
name="keywords"
content={content?.seo?.keywords?.join(', ') || 'luxury hotel, premium accommodation'}
/>
</Helmet>
{/* Hero Section - Logo and Image Only */}
<Box
sx={{
minHeight: '100vh',
background: `linear-gradient(rgba(0,0,0,0.35), rgba(0,0,0,0.35)), url("${heroImage}") center/cover`,
background: `linear-gradient(rgba(0,0,0,0.45), rgba(0,0,0,0.45)), url("${heroImage}") center/cover`,
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-end',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
textAlign: 'center',
position: 'relative',
pb: { xs: 25, md: 80},
}}
>
<Container maxWidth="lg">
<Container maxWidth="lg" sx={{ width: '100%' }}>
<motion.div
initial={{ opacity: 0, y: 50 }}
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 1, ease: 'easeOut' }}
>
{/* Logo Only */}
<Box
sx={{
display: 'flex',
justifyContent: 'center',
mb: 4,
}}
>
<img
<Box sx={{ display: 'flex', justifyContent: 'center', width: '100%' }}>
<Box
component="img"
src="/images/logo.png"
alt="Old Vine Hotel"
style={{
height: 'auto',
sx={{
width: 'auto',
maxHeight: '200px',
maxWidth: '100%',
maxWidth: '90%',
maxHeight: { xs: 140, md: 250 },
filter: 'drop-shadow(0 10px 24px rgba(0,0,0,0.65))',
}}
/>
</Box>
</motion.div>
</Container>
{/* Scroll Indicator */}
<Box
sx={{
position: 'absolute',
bottom: 30,
left: '50%',
transform: 'translateX(-50%)',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
animation: 'bounce 2s infinite',
}}
>
<Typography variant="body2" sx={{ mb: 1, opacity: 0.8 }}>
Scroll Down
</Typography>
<Box
sx={{
width: 2,
height: 30,
backgroundColor: 'white',
opacity: 0.6,
borderRadius: 1,
}}
/>
</Box>
</Box>
{/* Welcome Section */}
@@ -214,28 +178,11 @@ const Home = () => {
viewport={{ once: true }}
>
<Box textAlign="center" sx={{ mb: 6 }}>
<Typography
variant="h2"
component="h2"
sx={{
mb: 3,
// Use theme h2 font (Calistoga Regular - not bold)
color: 'primary.main',
fontWeight: 400,
}}
>
<Typography variant="h2" component="h2" sx={{ mb: 3, color: 'primary.main', fontWeight: 400 }}>
{welcomeTitle}
</Typography>
<Typography
variant="h5"
component="p"
sx={{
mb: 3,
color: 'text.secondary',
fontWeight: 300,
}}
>
<Typography variant="h5" component="p" sx={{ mb: 3, color: 'text.secondary', fontWeight: 300 }}>
{welcomeSubtitle}
</Typography>
@@ -246,7 +193,7 @@ const Home = () => {
mx: 'auto',
lineHeight: 1.8,
fontSize: '1.1rem',
whiteSpace: 'pre-line', // Preserve line breaks
whiteSpace: 'pre-line',
}}
>
{welcomeContent}
@@ -264,68 +211,74 @@ const Home = () => {
transition={{ duration: 0.8 }}
viewport={{ once: true }}
>
<Typography
variant="h3"
component="h2"
textAlign="center"
sx={{
mb: 6,
color: 'primary.main',
}}
>
<Typography variant="h3" component="h2" textAlign="center" sx={{ mb: 6, color: 'primary.main' }}>
{t('home.featuresTitle')}
</Typography>
<Grid container spacing={4}>
<Box
sx={{
display: 'grid',
gap: 4,
gridTemplateColumns: {
xs: '1fr',
sm: 'repeat(2, 1fr)',
md: 'repeat(5, 1fr)',
},
alignItems: 'stretch',
}}
>
{features.map((feature, index) => (
<Grid item xs={12} sm={6} md={3} key={index}>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: index * 0.1 }}
viewport={{ once: true }}
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: index * 0.08 }}
viewport={{ once: true }}
>
<Card
sx={{
height: '100%',
display: 'flex',
flexDirection: 'column',
textAlign: 'center',
p: 3,
border: 'none',
boxShadow: 3,
transition: 'all 0.3s ease',
'&:hover': { transform: 'translateY(-8px)', boxShadow: 6 },
}}
>
<Card
<Box sx={{ display: 'flex', justifyContent: 'center', mb: 2, color: 'primary.main' }}>
{feature.icon}
</Box>
<Typography
variant="h6"
component="h3"
sx={{
height: '100%',
textAlign: 'center',
p: 3,
border: 'none',
boxShadow: 3,
'&:hover': {
transform: 'translateY(-8px)',
boxShadow: 6,
},
transition: 'all 0.3s ease',
mb: 2,
fontWeight: 600,
lineHeight: 1.2,
minHeight: '2.4em',
}}
>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
mb: 2,
color: 'primary.main',
}}
>
{feature.icon}
</Box>
{feature.title}
</Typography>
<Typography
variant="h6"
component="h3"
sx={{ mb: 2, fontWeight: 600 }}
>
{feature.title}
</Typography>
<Typography variant="body2" color="text.secondary">
{feature.description}
</Typography>
</Card>
</motion.div>
</Grid>
<Typography
variant="body2"
color="text.secondary"
sx={{
lineHeight: 1.6,
minHeight: '4.8em',
}}
>
{feature.description}
</Typography>
</Card>
</motion.div>
))}
</Grid>
</Box>
</motion.div>
</Container>
</Box>
@@ -339,26 +292,11 @@ const Home = () => {
viewport={{ once: true }}
>
<Box textAlign="center" sx={{ mb: 6 }}>
<Typography
variant="h3"
component="h2"
sx={{
mb: 3,
color: 'primary.main',
}}
>
<Typography variant="h3" component="h2" sx={{ mb: 3, color: 'primary.main' }}>
{t('home.roomsTitle')}
</Typography>
<Typography
variant="h6"
component="p"
sx={{
mb: 4,
color: 'text.secondary',
fontWeight: 300,
}}
>
<Typography variant="h6" component="p" sx={{ mb: 4, color: 'text.secondary', fontWeight: 300 }}>
{t('home.roomsSubtitle')}
</Typography>
</Box>
@@ -380,18 +318,12 @@ const Home = () => {
alt={room.name}
sx={{
transition: 'transform 0.3s ease',
'&:hover': {
transform: 'scale(1.05)',
},
'&:hover': { transform: 'scale(1.05)' },
}}
/>
<CardContent sx={{ p: 3 }}>
<Typography
variant="h5"
component="h3"
sx={{ mb: 2 }}
>
<Typography variant="h5" component="h3" sx={{ mb: 2 }}>
{room.name}
</Typography>
@@ -401,12 +333,7 @@ const Home = () => {
key={idx}
label={feature}
size="small"
sx={{
mr: 1,
mb: 1,
backgroundColor: 'primary.main',
color: 'white',
}}
sx={{ mr: 1, mb: 1, backgroundColor: 'primary.main', color: 'white' }}
/>
))}
</Box>
@@ -441,7 +368,6 @@ const Home = () => {
</Box>
</motion.div>
</Container>
</>
);
};