final updates
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 19 KiB |
42
client/package-lock.json
generated
42
client/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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 |
@@ -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>
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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": "واتساب"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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: <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 l’hôtel')}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user