1
This commit is contained in:
parent
9b5dc68a86
commit
fcdc4650eb
@ -100,6 +100,8 @@ require('./auth/auth');
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.use('/api/auth', authRoutes);
|
||||
const publicBookingRoutes = require('./routes/public_booking');
|
||||
app.use('/api/public_booking', publicBookingRoutes);
|
||||
app.use('/api/file', fileRoutes);
|
||||
app.use('/api/pexels', pexelsRoutes);
|
||||
app.enable('trust proxy');
|
||||
|
||||
24
backend/src/routes/public_booking.js
Normal file
24
backend/src/routes/public_booking.js
Normal file
@ -0,0 +1,24 @@
|
||||
const express = require('express');
|
||||
const Booking_requestsService = require('../services/booking_requests');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/', wrapAsync(async (req, res) => {
|
||||
const data = req.body;
|
||||
|
||||
// Check if the data is wrapped in a "data" object, if not, use the body itself
|
||||
const bookingData = data.data ? data.data : data;
|
||||
|
||||
// Set default status to new and channel to website_form
|
||||
if (bookingData) {
|
||||
bookingData.status = 'new';
|
||||
bookingData.channel = 'website_form';
|
||||
bookingData.requested_at = new Date();
|
||||
}
|
||||
|
||||
await Booking_requestsService.create(bookingData, null);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
219
frontend/src/pages/book.tsx
Normal file
219
frontend/src/pages/book.tsx
Normal file
@ -0,0 +1,219 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { ReactElement } from 'react';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import axios from 'axios';
|
||||
import LayoutGuest from '../layouts/Guest';
|
||||
import { getPageTitle } from '../config';
|
||||
|
||||
export default function BookYourStay() {
|
||||
const [formData, setFormData] = useState({
|
||||
guest_full_name: '',
|
||||
guest_email: '',
|
||||
guest_phone: '',
|
||||
check_in: '',
|
||||
check_out: '',
|
||||
adults: 1,
|
||||
children: 0,
|
||||
special_requests: '',
|
||||
preferred_contact: 'WhatsApp',
|
||||
});
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [submitSuccess, setSubmitSuccess] = useState(false);
|
||||
const [errorMsg, setErrorMsg] = useState('');
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData(prev => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const validate = () => {
|
||||
if (!formData.guest_full_name) return 'Name is required.';
|
||||
if (!formData.guest_email) return 'Email is required.';
|
||||
if (!formData.guest_phone) return 'Phone is required.';
|
||||
if (!formData.check_in) return 'Check-in date is required.';
|
||||
if (!formData.check_out) return 'Check-out date is required.';
|
||||
|
||||
if (new Date(formData.check_out) <= new Date(formData.check_in)) {
|
||||
return 'Check-out date must be after check-in date.';
|
||||
}
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(formData.guest_email)) {
|
||||
return 'Please enter a valid email address.';
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setErrorMsg('');
|
||||
|
||||
const validationError = validate();
|
||||
if (validationError) {
|
||||
setErrorMsg(validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await axios.post('/public_booking', {
|
||||
data: formData
|
||||
});
|
||||
setSubmitSuccess(true);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setErrorMsg('An error occurred while submitting your request. Please try again or contact us directly.');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="font-sans text-[#1A1A1A] bg-[#F5EFE6] min-h-screen">
|
||||
<Head>
|
||||
<title>{getPageTitle('Book Your Stay')}</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;1,400&family=Inter:wght@300;400;500;600&family=Playfair+Display:ital,wght@0,400;0,600;0,700;1,400&display=swap" rel="stylesheet" />
|
||||
<style>{`
|
||||
.font-heading { font-family: 'Playfair Display', serif; }
|
||||
.font-body { font-family: 'Inter', sans-serif; }
|
||||
.font-accent { font-family: 'Cormorant Garamond', serif; }
|
||||
`}</style>
|
||||
</Head>
|
||||
|
||||
{/* Header */}
|
||||
<header className="bg-[#1E3D2B] text-[#FFFFFF] py-6 px-6 shadow-md relative z-10">
|
||||
<div className="max-w-4xl mx-auto flex justify-between items-center">
|
||||
<Link href="/" className="font-heading text-2xl text-[#D6B56C]">Hôtel Marhaba</Link>
|
||||
<Link href="/" className="font-body text-sm hover:text-[#D6B56C] transition-colors">← Back to Home</Link>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="max-w-3xl mx-auto px-6 py-12">
|
||||
<div className="bg-[#FFFFFF] shadow-xl rounded-xl p-8 md:p-12 relative overflow-hidden">
|
||||
|
||||
{submitSuccess ? (
|
||||
<div className="text-center py-16">
|
||||
<div className="w-20 h-20 bg-[#D6B56C] rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<svg className="w-10 h-10 text-[#FFFFFF]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="font-heading text-4xl text-[#1E3D2B] mb-4">Request Received</h2>
|
||||
<p className="font-body text-lg text-[#1A1A1A] mb-8">
|
||||
Thank you for choosing Hôtel Marhaba. Your booking request has been successfully submitted. Our team will contact you shortly to confirm your reservation and arrange payment.
|
||||
</p>
|
||||
<div className="bg-[#F5EFE6] p-6 rounded-lg text-left mb-8 inline-block max-w-lg mx-auto w-full">
|
||||
<p className="font-body font-semibold mb-2">Next Steps:</p>
|
||||
<ul className="list-disc list-inside font-body text-sm space-y-1 ml-4">
|
||||
<li>We will review your requested dates and room availability.</li>
|
||||
<li>You will receive a confirmation call or email within 24 hours.</li>
|
||||
<li>Payment details will be provided upon confirmation.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<br/>
|
||||
<Link href="/" className="bg-[#1E3D2B] hover:bg-[#152e20] text-[#FFFFFF] font-body font-medium py-3 px-8 rounded transition-colors duration-300">
|
||||
Return to Home
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-center mb-10">
|
||||
<h1 className="font-heading text-4xl md:text-5xl text-[#1E3D2B] mb-4">Book Your Stay</h1>
|
||||
<p className="font-body text-[#1A1A1A] text-lg max-w-lg mx-auto">
|
||||
Submit your reservation request below, and we will contact you promptly to confirm your booking at our historic oasis.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{errorMsg && (
|
||||
<div className="bg-red-50 text-red-600 p-4 rounded mb-6 font-body text-sm border-l-4 border-red-500">
|
||||
{errorMsg}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6 font-body">
|
||||
{/* Dates & Guests */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-[#1E3D2B] mb-1">Check-in Date *</label>
|
||||
<input type="date" name="check_in" value={formData.check_in} onChange={handleChange} required className="w-full border border-gray-300 rounded px-4 py-3 focus:ring-2 focus:ring-[#D6B56C] focus:border-transparent outline-none" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-[#1E3D2B] mb-1">Check-out Date *</label>
|
||||
<input type="date" name="check_out" value={formData.check_out} onChange={handleChange} required className="w-full border border-gray-300 rounded px-4 py-3 focus:ring-2 focus:ring-[#D6B56C] focus:border-transparent outline-none" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-[#1E3D2B] mb-1">Adults</label>
|
||||
<select name="adults" value={formData.adults} onChange={handleChange} className="w-full border border-gray-300 rounded px-4 py-3 focus:ring-2 focus:ring-[#D6B56C] focus:border-transparent outline-none">
|
||||
{[1, 2, 3, 4, 5, 6].map(n => <option key={n} value={n}>{n}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-[#1E3D2B] mb-1">Children</label>
|
||||
<select name="children" value={formData.children} onChange={handleChange} className="w-full border border-gray-300 rounded px-4 py-3 focus:ring-2 focus:ring-[#D6B56C] focus:border-transparent outline-none">
|
||||
{[0, 1, 2, 3, 4].map(n => <option key={n} value={n}>{n}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="border-gray-200" />
|
||||
|
||||
{/* Contact Info */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-semibold text-[#1E3D2B] mb-1">Full Name *</label>
|
||||
<input type="text" name="guest_full_name" value={formData.guest_full_name} onChange={handleChange} required placeholder="John Doe" className="w-full border border-gray-300 rounded px-4 py-3 focus:ring-2 focus:ring-[#D6B56C] focus:border-transparent outline-none" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-[#1E3D2B] mb-1">Email Address *</label>
|
||||
<input type="email" name="guest_email" value={formData.guest_email} onChange={handleChange} required placeholder="john@example.com" className="w-full border border-gray-300 rounded px-4 py-3 focus:ring-2 focus:ring-[#D6B56C] focus:border-transparent outline-none" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-[#1E3D2B] mb-1">Phone Number *</label>
|
||||
<input type="tel" name="guest_phone" value={formData.guest_phone} onChange={handleChange} required placeholder="+213 00 00 00 00" className="w-full border border-gray-300 rounded px-4 py-3 focus:ring-2 focus:ring-[#D6B56C] focus:border-transparent outline-none" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Additional Info */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-[#1E3D2B] mb-1">Special Requests or Notes</label>
|
||||
<textarea name="special_requests" value={formData.special_requests} onChange={handleChange} rows={4} placeholder="Any specific room preferences or arrival time?" className="w-full border border-gray-300 rounded px-4 py-3 focus:ring-2 focus:ring-[#D6B56C] focus:border-transparent outline-none"></textarea>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className={`w-full bg-[#1E3D2B] text-[#FFFFFF] font-body font-medium py-4 rounded text-lg transition-colors duration-300 flex justify-center items-center ${isSubmitting ? 'opacity-70 cursor-not-allowed' : 'hover:bg-[#152e20]'}`}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
Submitting Request...
|
||||
</>
|
||||
) : 'Submit Booking Request'}
|
||||
</button>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer className="bg-[#1A1A1A] py-8 text-[#FFFFFF] mt-12">
|
||||
<div className="max-w-4xl mx-auto px-6 text-center font-body text-sm text-gray-400">
|
||||
<p>© 2026 Hôtel Marhaba. All rights reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
BookYourStay.getLayout = function getLayout(page: ReactElement) {
|
||||
return <>{page}</>;
|
||||
};
|
||||
@ -1,166 +1,207 @@
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
import type { ReactElement } from 'react';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import BaseButton from '../components/BaseButton';
|
||||
import CardBox from '../components/CardBox';
|
||||
import SectionFullScreen from '../components/SectionFullScreen';
|
||||
import LayoutGuest from '../layouts/Guest';
|
||||
import BaseDivider from '../components/BaseDivider';
|
||||
import BaseButtons from '../components/BaseButtons';
|
||||
import { getPageTitle } from '../config';
|
||||
import { useAppSelector } from '../stores/hooks';
|
||||
import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
|
||||
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
|
||||
|
||||
|
||||
export default function Starter() {
|
||||
const [illustrationImage, setIllustrationImage] = useState({
|
||||
src: undefined,
|
||||
photographer: undefined,
|
||||
photographer_url: undefined,
|
||||
})
|
||||
const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
|
||||
const [contentType, setContentType] = useState('image');
|
||||
const [contentPosition, setContentPosition] = useState('left');
|
||||
const textColor = useAppSelector((state) => state.style.linkColor);
|
||||
|
||||
const title = 'Hotel Marhaba Luxury Site'
|
||||
|
||||
// Fetch Pexels image/video
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
const image = await getPexelsImage();
|
||||
const video = await getPexelsVideo();
|
||||
setIllustrationImage(image);
|
||||
setIllustrationVideo(video);
|
||||
}
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const imageBlock = (image) => (
|
||||
<div
|
||||
className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'
|
||||
style={{
|
||||
backgroundImage: `${
|
||||
image
|
||||
? `url(${image?.src?.original})`
|
||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
||||
}`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'left center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}
|
||||
>
|
||||
<div className='flex justify-center w-full bg-blue-300/20'>
|
||||
<a
|
||||
className='text-[8px]'
|
||||
href={image?.photographer_url}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Photo by {image?.photographer} on Pexels
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const videoBlock = (video) => {
|
||||
if (video?.video_files?.length > 0) {
|
||||
return (
|
||||
<div className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'>
|
||||
<video
|
||||
className='absolute top-0 left-0 w-full h-full object-cover'
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
>
|
||||
<source src={video?.video_files[0]?.link} type='video/mp4'/>
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
|
||||
<a
|
||||
className='text-[8px]'
|
||||
href={video?.user?.url}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Video by {video.user.name} on Pexels
|
||||
</a>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={
|
||||
contentPosition === 'background'
|
||||
? {
|
||||
backgroundImage: `${
|
||||
illustrationImage
|
||||
? `url(${illustrationImage.src?.original})`
|
||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
||||
}`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'left center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<div className="font-sans text-[#1A1A1A] bg-[#F5EFE6] min-h-screen">
|
||||
<Head>
|
||||
<title>{getPageTitle('Starter Page')}</title>
|
||||
<title>{getPageTitle('Welcome')}</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;1,400&family=Inter:wght@300;400;500;600&family=Playfair+Display:ital,wght@0,400;0,600;0,700;1,400&display=swap" rel="stylesheet" />
|
||||
<style>{`
|
||||
.font-heading { font-family: 'Playfair Display', serif; }
|
||||
.font-body { font-family: 'Inter', sans-serif; }
|
||||
.font-accent { font-family: 'Cormorant Garamond', serif; }
|
||||
`}</style>
|
||||
</Head>
|
||||
|
||||
<SectionFullScreen bg='violet'>
|
||||
<div
|
||||
className={`flex ${
|
||||
contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'
|
||||
} min-h-screen w-full`}
|
||||
>
|
||||
{contentType === 'image' && contentPosition !== 'background'
|
||||
? imageBlock(illustrationImage)
|
||||
: null}
|
||||
{contentType === 'video' && contentPosition !== 'background'
|
||||
? videoBlock(illustrationVideo)
|
||||
: null}
|
||||
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
|
||||
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
|
||||
<CardBoxComponentTitle title="Welcome to your Hotel Marhaba Luxury Site app!"/>
|
||||
|
||||
<div className="space-y-3">
|
||||
<p className='text-center text-gray-500'>This is a React.js/Node.js app generated by the <a className={`${textColor}`} href="https://flatlogic.com/generator">Flatlogic Web App Generator</a></p>
|
||||
<p className='text-center text-gray-500'>For guides and documentation please check
|
||||
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p>
|
||||
</div>
|
||||
|
||||
<BaseButtons>
|
||||
<BaseButton
|
||||
href='/login'
|
||||
label='Login'
|
||||
color='info'
|
||||
className='w-full'
|
||||
/>
|
||||
|
||||
</BaseButtons>
|
||||
</CardBox>
|
||||
{/* Hero Section */}
|
||||
<section className="relative w-full h-screen min-h-[600px] flex items-center justify-center overflow-hidden">
|
||||
<div
|
||||
className="absolute inset-0 z-0 bg-cover bg-center"
|
||||
style={{
|
||||
backgroundImage: "url('https://www.fernandpouillon.com/uploads/3/9/3/8/3938813/laghouatt-22_14_orig.jpg')",
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 bg-[#1A1A1A] bg-opacity-40"></div>
|
||||
<div className="absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-t from-[#F5EFE6] to-transparent"></div>
|
||||
</div>
|
||||
</div>
|
||||
</SectionFullScreen>
|
||||
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
|
||||
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. All rights reserved</p>
|
||||
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 text-center px-6 max-w-4xl mx-auto mt-20">
|
||||
<h1 className="font-heading text-5xl md:text-7xl text-[#FFFFFF] mb-6 drop-shadow-lg">
|
||||
Welcome to Hôtel Marhaba
|
||||
</h1>
|
||||
<p className="font-body text-lg md:text-2xl text-[#FFFFFF] mb-10 font-light drop-shadow-md max-w-2xl mx-auto">
|
||||
A peaceful oasis in the heart of Laghouat, where architecture, gardens, and Algerian hospitality meet.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row justify-center gap-4">
|
||||
<Link href="/book" className="bg-[#D6B56C] hover:bg-[#c2a159] text-[#1A1A1A] font-body font-medium py-3 px-8 rounded transition-colors duration-300">
|
||||
Book Your Stay
|
||||
</Link>
|
||||
<Link href="#discover" className="bg-transparent border border-[#FFFFFF] text-[#FFFFFF] hover:bg-[#FFFFFF] hover:text-[#1A1A1A] font-body font-medium py-3 px-8 rounded transition-colors duration-300">
|
||||
Discover the Hotel
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Trust Section */}
|
||||
<section id="trust" className="py-20 bg-[#F5EFE6]">
|
||||
<div className="max-w-6xl mx-auto px-6">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="font-heading text-4xl text-[#1E3D2B] mb-4">Loved by Travelers</h2>
|
||||
<div className="flex items-center justify-center gap-2 mb-2">
|
||||
<span className="text-[#D6B56C] text-2xl">★</span>
|
||||
<span className="text-[#D6B56C] text-2xl">★</span>
|
||||
<span className="text-[#D6B56C] text-2xl">★</span>
|
||||
<span className="text-[#D6B56C] text-2xl">★</span>
|
||||
<span className="text-gray-300 text-2xl">★</span>
|
||||
</div>
|
||||
<p className="font-body text-[#1A1A1A] font-medium">4.0 Average Rating</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
<div className="bg-[#FFFFFF] p-8 rounded-xl shadow-sm flex flex-col justify-between border-t-4 border-[#D6B56C]">
|
||||
<p className="font-accent italic text-lg text-[#1A1A1A] mb-4">"Excellent rooms and a very helpful manager who speaks good English."</p>
|
||||
<p className="font-body text-sm font-semibold text-[#1E3D2B]">— Guest Review</p>
|
||||
</div>
|
||||
<div className="bg-[#FFFFFF] p-8 rounded-xl shadow-sm flex flex-col justify-between border-t-4 border-[#1E3D2B]">
|
||||
<p className="font-accent italic text-lg text-[#1A1A1A] mb-4">"The garden is like an oasis."</p>
|
||||
<p className="font-body text-sm font-semibold text-[#1E3D2B]">— Visitor</p>
|
||||
</div>
|
||||
<div className="bg-[#FFFFFF] p-8 rounded-xl shadow-sm flex flex-col justify-between border-t-4 border-[#D6B56C]">
|
||||
<p className="font-accent italic text-lg text-[#1A1A1A] mb-4">"One of the best hotels in Laghouat."</p>
|
||||
<p className="font-body text-sm font-semibold text-[#1E3D2B]">— Google Review</p>
|
||||
</div>
|
||||
<div className="bg-[#FFFFFF] p-8 rounded-xl shadow-sm flex flex-col justify-between border-t-4 border-[#1E3D2B]">
|
||||
<p className="font-accent italic text-lg text-[#1A1A1A] mb-4">"Beautiful architecture and pleasant surroundings."</p>
|
||||
<p className="font-body text-sm font-semibold text-[#1E3D2B]">— Traveler</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* The Story & Architecture Section */}
|
||||
<section id="discover" className="py-24 bg-[#FFFFFF]">
|
||||
<div className="max-w-6xl mx-auto px-6">
|
||||
<div className="flex flex-col lg:flex-row items-center gap-16">
|
||||
<div className="lg:w-1/2">
|
||||
<h2 className="font-heading text-sm uppercase tracking-widest text-[#D6B56C] mb-2">The Story</h2>
|
||||
<h3 className="font-heading text-4xl md:text-5xl text-[#1E3D2B] mb-6">A Historic Architectural Oasis</h3>
|
||||
<p className="font-body text-[#1A1A1A] mb-6 leading-relaxed text-lg">
|
||||
Designed in the spirit of the legendary architect <strong>Fernand Pouillon</strong>, Hôtel Marhaba is one of Laghouat's most distinctive landmarks. The property embodies a profound respect for the surrounding landscape and traditional Algerian aesthetics.
|
||||
</p>
|
||||
<p className="font-body text-[#1A1A1A] mb-8 leading-relaxed">
|
||||
Having recently undergone a thoughtful renovation, the hotel effortlessly blends historical charm with modern comfort. Every archway and stone path tells a story of hospitality that has welcomed travelers to the Sahara for generations.
|
||||
</p>
|
||||
<Link href="/book" className="inline-block border-b-2 border-[#D6B56C] text-[#1E3D2B] font-heading font-semibold pb-1 hover:text-[#D6B56C] transition-colors">
|
||||
Book Your Experience
|
||||
</Link>
|
||||
</div>
|
||||
<div className="lg:w-1/2">
|
||||
<img src="https://imgv2-2-f.scribdassets.com/img/document/929563449/original/c3e373047e/1?v=1" alt="Historic Architecture" className="w-full h-[500px] object-cover rounded-2xl shadow-xl" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Rooms & Garden Section */}
|
||||
<section className="py-24 bg-[#F5EFE6]">
|
||||
<div className="max-w-6xl mx-auto px-6">
|
||||
<div className="flex flex-col-reverse lg:flex-row items-center gap-16">
|
||||
<div className="lg:w-1/2 grid grid-cols-2 gap-4">
|
||||
<img src="https://images.trvl-media.com/lodging/38000000/37220000/37215100/37215091/86a76116.jpg?impolicy=resizecrop&ra=fill&rh=575&rw=575" alt="Hotel Room" className="w-full h-72 object-cover rounded-tl-[3rem] rounded-br-[3rem] shadow-lg" />
|
||||
<img src="https://media.joinup.travel/storage/hotel/50678/photos/20.jpg" alt="Oasis Garden" className="w-full h-72 object-cover rounded-tr-[3rem] rounded-bl-[3rem] shadow-lg mt-12" />
|
||||
</div>
|
||||
<div className="lg:w-1/2">
|
||||
<h2 className="font-heading text-sm uppercase tracking-widest text-[#D6B56C] mb-2">Rest & Relax</h2>
|
||||
<h3 className="font-heading text-4xl md:text-5xl text-[#1E3D2B] mb-6">Tranquil Rooms & Lush Gardens</h3>
|
||||
<p className="font-body text-[#1A1A1A] mb-6 leading-relaxed text-lg">
|
||||
Step away from the vibrant energy of the city center into our peaceful oasis. Our rooms offer excellent value and quiet comfort, featuring modern amenities wrapped in traditional design.
|
||||
</p>
|
||||
<p className="font-body text-[#1A1A1A] mb-8 leading-relaxed">
|
||||
Wander through our spectacular interior gardens, shaded by towering palms and echoing with the sound of fountains. It is the perfect place to enjoy a morning coffee or an evening tea, surrounded by nature.
|
||||
</p>
|
||||
<Link href="/book" className="inline-block bg-[#1E3D2B] text-[#FFFFFF] font-body font-medium py-3 px-8 rounded hover:bg-[#152e20] transition-colors">
|
||||
View Availability
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Amenities / Highlights */}
|
||||
<section className="py-20 bg-[#1A1A1A] text-[#FFFFFF]">
|
||||
<div className="max-w-6xl mx-auto px-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-12 text-center">
|
||||
<div>
|
||||
<div className="text-[#D6B56C] text-4xl mb-4">📍</div>
|
||||
<h4 className="font-heading text-2xl mb-2">Prime Location</h4>
|
||||
<p className="font-body text-gray-400">Conveniently situated in Laghouat city center, putting the best of the city right at your doorstep.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[#D6B56C] text-4xl mb-4">🍽️</div>
|
||||
<h4 className="font-heading text-2xl mb-2">Authentic Dining</h4>
|
||||
<p className="font-body text-gray-400">Enjoy traditional Algerian flavors and international dishes in our beautifully appointed restaurant.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[#D6B56C] text-4xl mb-4">🤝</div>
|
||||
<h4 className="font-heading text-2xl mb-2">Friendly Staff</h4>
|
||||
<p className="font-body text-gray-400">Our dedicated team is celebrated for their warm hospitality and readiness to assist in multiple languages.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Booking / CTA Section */}
|
||||
<section id="book" className="py-32 bg-[#1E3D2B] text-[#FFFFFF] relative overflow-hidden">
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<div className="w-[800px] h-[800px] border border-white rounded-full absolute -top-[400px] -right-[400px]"></div>
|
||||
<div className="w-[600px] h-[600px] border border-white rounded-full absolute -bottom-[300px] -left-[300px]"></div>
|
||||
</div>
|
||||
<div className="relative z-10 max-w-4xl mx-auto px-6 text-center">
|
||||
<h2 className="font-heading text-5xl mb-6">Experience Laghouat's Architectural Gem</h2>
|
||||
<p className="font-body text-xl mb-12 text-[#F5EFE6] opacity-90 max-w-2xl mx-auto">
|
||||
Ready to discover where history meets comfort? Secure your dates today.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row justify-center gap-6">
|
||||
<Link href="/book" className="bg-[#D6B56C] hover:bg-[#c2a159] text-[#1A1A1A] font-body font-medium py-4 px-12 rounded text-lg transition-colors duration-300">
|
||||
Book Your Stay
|
||||
</Link>
|
||||
<a href="https://wa.me/21300000000" target="_blank" rel="noreferrer" className="bg-transparent border-2 border-[#D6B56C] text-[#D6B56C] hover:bg-[#D6B56C] hover:text-[#1A1A1A] font-body font-medium py-4 px-12 rounded text-lg transition-colors duration-300">
|
||||
Contact via WhatsApp
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-[#1A1A1A] py-16 text-[#FFFFFF] border-t border-gray-800">
|
||||
<div className="max-w-6xl mx-auto px-6 flex flex-col md:flex-row justify-between items-center">
|
||||
<div className="mb-8 md:mb-0 text-center md:text-left">
|
||||
<h4 className="font-heading text-3xl text-[#D6B56C] mb-2">Hôtel Marhaba</h4>
|
||||
<p className="font-body text-sm text-gray-400">A Desert Oasis of Hospitality.</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap justify-center gap-6 font-body text-sm">
|
||||
<Link href="/book" className="hover:text-[#D6B56C] transition-colors">Book Now</Link>
|
||||
<Link href="#" className="hover:text-[#D6B56C] transition-colors">Privacy Policy</Link>
|
||||
<Link href="#" className="hover:text-[#D6B56C] transition-colors">Terms of Service</Link>
|
||||
<span className="text-gray-700">|</span>
|
||||
<Link href="/login" className="hover:text-[#D6B56C] transition-colors text-gray-400">Staff Login</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-6xl mx-auto px-6 mt-12 text-center text-xs text-gray-600 font-body">
|
||||
© 2026 Hôtel Marhaba, Laghouat. Built with care.
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Starter.getLayout = function getLayout(page: ReactElement) {
|
||||
return <LayoutGuest>{page}</LayoutGuest>;
|
||||
};
|
||||
|
||||
return <>{page}</>;
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user