From fcdc4650eb25c268ff8d1b886686ed8f76793e72 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 9 Mar 2026 03:44:15 +0000 Subject: [PATCH] 1 --- backend/src/index.js | 2 + backend/src/routes/public_booking.js | 24 ++ frontend/src/pages/book.tsx | 219 +++++++++++++++++ frontend/src/pages/index.tsx | 339 +++++++++++++++------------ 4 files changed, 435 insertions(+), 149 deletions(-) create mode 100644 backend/src/routes/public_booking.js create mode 100644 frontend/src/pages/book.tsx diff --git a/backend/src/index.js b/backend/src/index.js index d6f9743..ad002e2 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -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'); diff --git a/backend/src/routes/public_booking.js b/backend/src/routes/public_booking.js new file mode 100644 index 0000000..da7966b --- /dev/null +++ b/backend/src/routes/public_booking.js @@ -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; \ No newline at end of file diff --git a/frontend/src/pages/book.tsx b/frontend/src/pages/book.tsx new file mode 100644 index 0000000..821ea48 --- /dev/null +++ b/frontend/src/pages/book.tsx @@ -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) => { + 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 ( +
+ + {getPageTitle('Book Your Stay')} + + + + + + + {/* Header */} +
+
+ Hôtel Marhaba + ← Back to Home +
+
+ +
+
+ + {submitSuccess ? ( +
+
+ + + +
+

Request Received

+

+ 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. +

+
+

Next Steps:

+
    +
  • We will review your requested dates and room availability.
  • +
  • You will receive a confirmation call or email within 24 hours.
  • +
  • Payment details will be provided upon confirmation.
  • +
+
+
+ + Return to Home + +
+ ) : ( + <> +
+

Book Your Stay

+

+ Submit your reservation request below, and we will contact you promptly to confirm your booking at our historic oasis. +

+
+ + {errorMsg && ( +
+ {errorMsg} +
+ )} + +
+ {/* Dates & Guests */} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + {/* Contact Info */} +
+
+ + +
+
+ + +
+
+ + +
+
+ + {/* Additional Info */} +
+ + +
+ + +
+ + )} +
+
+ +
+
+

© 2026 Hôtel Marhaba. All rights reserved.

+
+
+
+ ); +} + +BookYourStay.getLayout = function getLayout(page: ReactElement) { + return <>{page}; +}; \ No newline at end of file diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index f716543..28afaa0 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -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) => ( -
-
- - Photo by {image?.photographer} on Pexels - -
-
- ); - - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -
- -
- - Video by {video.user.name} on Pexels - -
-
) - } - }; - return ( -
+
- {getPageTitle('Starter Page')} + {getPageTitle('Welcome')} + + + + - -
- {contentType === 'image' && contentPosition !== 'background' - ? imageBlock(illustrationImage) - : null} - {contentType === 'video' && contentPosition !== 'background' - ? videoBlock(illustrationVideo) - : null} -
- - - -
-

This is a React.js/Node.js app generated by the Flatlogic Web App Generator

-

For guides and documentation please check - your local README.md and the Flatlogic documentation

-
- - - - - -
+ {/* Hero Section */} +
+
+
+
-
- -
-

© 2026 {title}. All rights reserved

- - Privacy Policy - -
+
+

+ Welcome to Hôtel Marhaba +

+

+ A peaceful oasis in the heart of Laghouat, where architecture, gardens, and Algerian hospitality meet. +

+
+ + Book Your Stay + + + Discover the Hotel + +
+
+ + + {/* Trust Section */} +
+
+
+

Loved by Travelers

+
+ + + + + +
+

4.0 Average Rating

+
+ +
+
+

"Excellent rooms and a very helpful manager who speaks good English."

+

— Guest Review

+
+
+

"The garden is like an oasis."

+

— Visitor

+
+
+

"One of the best hotels in Laghouat."

+

— Google Review

+
+
+

"Beautiful architecture and pleasant surroundings."

+

— Traveler

+
+
+
+
+ + {/* The Story & Architecture Section */} +
+
+
+
+

The Story

+

A Historic Architectural Oasis

+

+ Designed in the spirit of the legendary architect Fernand Pouillon, 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. +

+

+ 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. +

+ + Book Your Experience + +
+
+ Historic Architecture +
+
+
+
+ + {/* Rooms & Garden Section */} +
+
+
+
+ Hotel Room + Oasis Garden +
+
+

Rest & Relax

+

Tranquil Rooms & Lush Gardens

+

+ 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. +

+

+ 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. +

+ + View Availability + +
+
+
+
+ + {/* Amenities / Highlights */} +
+
+
+
+
📍
+

Prime Location

+

Conveniently situated in Laghouat city center, putting the best of the city right at your doorstep.

+
+
+
🍽️
+

Authentic Dining

+

Enjoy traditional Algerian flavors and international dishes in our beautifully appointed restaurant.

+
+
+
🤝
+

Friendly Staff

+

Our dedicated team is celebrated for their warm hospitality and readiness to assist in multiple languages.

+
+
+
+
+ + {/* Booking / CTA Section */} +
+
+
+
+
+
+

Experience Laghouat's Architectural Gem

+

+ Ready to discover where history meets comfort? Secure your dates today. +

+
+ + Book Your Stay + + + Contact via WhatsApp + +
+
+
+ + {/* Footer */} +
+
+
+

Hôtel Marhaba

+

A Desert Oasis of Hospitality.

+
+
+ Book Now + Privacy Policy + Terms of Service + | + Staff Login +
+
+
+ © 2026 Hôtel Marhaba, Laghouat. Built with care. +
+
); } Starter.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - + return <>{page}; +}; \ No newline at end of file