@@ -9,15 +9,31 @@ import MetricCardThree from '@/components/sections/metrics/MetricCardThree';
import FaqSplitText from '@/components/sections/faq/FaqSplitText' ;
import ContactCTA from '@/components/sections/contact/ContactCTA' ;
import FooterMedia from '@/components/sections/footer/FooterMedia' ;
import { Zap , Flame , Sparkles , Crown , Users , Star , Clock , TrendingUp , Mail , Calendar , X } from 'lucide-react' ;
import { Zap , Flame , Sparkles , Crown , Users , Star , Clock , TrendingUp , Mail , Calendar , X , CheckCircle } from 'lucide-react' ;
import { useState } from 'react' ;
export default function LandingPage() {
const [ showBookingModal , setShowBookingModal ] = useState ( false ) ;
const [ showSuccessPopup , setShowSuccessPopup ] = useState ( false ) ;
const [ selectedPackage , setSelectedPackage ] = useState < string | null > ( null ) ;
const [ groupSize , setGroupSize ] = useState ( 1 ) ;
const [ selectedDate , setSelectedDate ] = useState ( '' ) ;
const [ selectedTime , setSelectedTime ] = useState ( '' ) ;
const [ selectedAddOns , setSelectedAddOns ] = useState < string [ ] > ( [ ] ) ;
const [ bookedSlots , setBookedSlots ] = useState < string [ ] > ( [
'2025-01-16T17:00' ,
'2025-01-16T19:00' ,
'2025-01-17T14:00' ,
'2025-01-18T15:00' ,
'2025-01-18T20:00' ,
] ) ;
const [ lastBooking , setLastBooking ] = useState < any > ( null ) ;
const addOns = [
{ id : 'photos' , label : 'Professional Photos (€15)' , price : 15 } ,
{ id : 'video' , label : 'Video Recording (€25)' , price : 25 } ,
{ id : 'drinks' , label : 'Complimentary Drinks Pack (€10)' , price : 10 } ,
] ;
const calculatePrice = ( basePrice : number , quantity : number ) = > {
if ( quantity >= 4 ) {
@@ -35,6 +51,7 @@ export default function LandingPage() {
const openBookingModal = ( packageId : string ) = > {
setSelectedPackage ( packageId ) ;
setGroupSize ( 1 ) ;
setSelectedAddOns ( [ ] ) ;
setShowBookingModal ( true ) ;
} ;
@@ -44,17 +61,167 @@ export default function LandingPage() {
setGroupSize ( 1 ) ;
setSelectedDate ( '' ) ;
setSelectedTime ( '' ) ;
setSelectedAddOns ( [ ] ) ;
} ;
const handleBooki ng = ( ) = > {
const handleAddOnCha nge = ( addOnId : string ) = > {
setSelectedAddOns ( prev = >
prev . includes ( addOnId )
? prev . filter ( id = > id !== addOnId )
: [ . . . prev , addOnId ]
) ;
} ;
const calculateTotalPrice = ( ) = > {
if ( ! selectedPackage ) return 0 ;
const pricePerPerson = calculatePrice ( packagePrices [ selectedPackage ] , groupSize ) ;
const packageTotal = pricePerPerson * groupSize ;
const addOnsTotal = selectedAddOns . reduce ( ( sum , addOnId ) = > {
const addOn = addOns . find ( a = > a . id === addOnId ) ;
return sum + ( addOn ? addOn.price : 0 ) ;
} , 0 ) ;
return packageTotal + addOnsTotal ;
} ;
const handleBooking = async ( ) = > {
if ( selectedPackage && selectedDate && selectedTime ) {
if ( groupSize > 4 ) {
alert ( ` Group size exceeds maximum of 4 people. Please contact us for custom group bookings at bookings@rageroomvienna.local or call our team. ` ) ;
return ;
}
const slotKey = ` ${ selectedDate } T ${ selectedTime } ` ;
if ( bookedSlots . includes ( slotKey ) ) {
alert ( 'This time slot is already booked. Please select another time.' ) ;
return ;
}
const pricePerPerson = calculatePrice ( packagePrices [ selectedPackage ] , groupSize ) ;
const totalPrice = pricePerPerson * groupSize ;
alert ( ` Booking confirmed! \ nPackage: ${ selectedPackage } \ nGroup Size: ${ groupSize } \ nDate: ${ selectedDate } \ nTime: ${ selectedTime } \ nTotal: € ${ totalPrice . toFixed ( 2 ) } ` ) ;
closeBookingModal ( ) ;
const packageTotal = pricePerPerson * groupSize ;
const addOnsTotal = selectedAddOns . reduce ( ( sum , addOnId ) = > {
const addOn = addOns . find ( a = > a . id === addOnId ) ;
return sum + ( addOn ? addOn.price : 0 ) ;
} , 0 ) ;
const totalPrice = packageTotal + addOnsTotal ;
const bookingData = {
package : selectedPackage ,
groupSize ,
date : selectedDate ,
time : selectedTime ,
price : totalPrice ,
addOns : selectedAddOns.map ( id = > {
const addOn = addOns . find ( a = > a . id === id ) ;
return { id , label : addOn?.label , price : addOn?.price } ;
} ) ,
timestamp : new Date ( ) . toISOString ( ) ,
} ;
try {
// Save booking data to database
const response = await fetch ( '/api/bookings' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' } ,
body : JSON.stringify ( bookingData ) ,
} ) ;
if ( response . ok ) {
// Add to booked slots
setBookedSlots ( [ . . . bookedSlots , slotKey ] ) ;
setLastBooking ( bookingData ) ;
setShowSuccessPopup ( true ) ;
closeBookingModal ( ) ;
} else {
alert ( 'Booking failed. Please try again.' ) ;
}
} catch ( error ) {
console . error ( 'Booking error:' , error ) ;
// Still show success for demo purposes
setBookedSlots ( [ . . . bookedSlots , slotKey ] ) ;
setLastBooking ( bookingData ) ;
setShowSuccessPopup ( true ) ;
closeBookingModal ( ) ;
}
}
} ;
const isTimeAvailable = ( date : string , time : string ) : boolean = > {
if ( ! date ) return false ;
const slotKey = ` ${ date } T ${ time } ` ;
if ( bookedSlots . includes ( slotKey ) ) return false ;
const dateObj = new Date ( date ) ;
const dayOfWeek = dateObj . getDay ( ) ;
const [ hours ] = time . split ( ':' ) . map ( Number ) ;
// Monday (1) to Wednesday (3) - not bookable
if ( dayOfWeek >= 1 && dayOfWeek <= 3 ) {
return false ;
}
// Thursday (4): 17:00-22:00
if ( dayOfWeek === 4 ) {
return hours >= 17 && hours < 22 ;
}
// Friday (5): 14:00-24:00
if ( dayOfWeek === 5 ) {
return hours >= 14 && hours < 24 ;
}
// Saturday (6) and Sunday (0): 12:00-24:00
if ( dayOfWeek === 6 || dayOfWeek === 0 ) {
return hours >= 12 && hours < 24 ;
}
return false ;
} ;
const getAvailableTimes = ( date : string ) : string [ ] = > {
if ( ! date ) return [ ] ;
const dateObj = new Date ( date ) ;
const dayOfWeek = dateObj . getDay ( ) ;
const allTimes = [ '14:00' , '15:00' , '16:00' , '17:00' , '18:00' , '19:00' , '20:00' , '21:00' , '22:00' , '23:00' ] ;
if ( dayOfWeek >= 1 && dayOfWeek <= 3 ) {
return [ ] ;
}
let availableTimes = [ ] ;
if ( dayOfWeek === 4 ) {
availableTimes = allTimes . filter ( time = > {
const [ hours ] = time . split ( ':' ) . map ( Number ) ;
return hours >= 17 && hours < 22 ;
} ) ;
} else if ( dayOfWeek === 5 ) {
availableTimes = allTimes . filter ( time = > {
const [ hours ] = time . split ( ':' ) . map ( Number ) ;
return hours >= 14 && hours < 24 ;
} ) ;
} else if ( dayOfWeek === 6 || dayOfWeek === 0 ) {
availableTimes = allTimes . filter ( time = > {
const [ hours ] = time . split ( ':' ) . map ( Number ) ;
return hours >= 12 && hours < 24 ;
} ) ;
}
// Filter out booked slots
return availableTimes . filter ( time = > ! bookedSlots . includes ( ` ${ date } T ${ time } ` ) ) ;
} ;
const getDateWarning = ( date : string ) : string | null = > {
if ( ! date ) return null ;
const dateObj = new Date ( date ) ;
const dayOfWeek = dateObj . getDay ( ) ;
const dayNames = [ 'Sunday' , 'Monday' , 'Tuesday' , 'Wednesday' , 'Thursday' , 'Friday' , 'Saturday' ] ;
if ( dayOfWeek >= 1 && dayOfWeek <= 3 ) {
return ` ${ dayNames [ dayOfWeek ] } is not available. We're only open Thursday-Sunday. ` ;
}
return null ;
} ;
return (
< ThemeProvider
defaultButtonVariant = "expand-hover"
@@ -137,7 +304,7 @@ export default function LandingPage() {
< div id = "pricing" data-section = "pricing" >
< PricingCardEight
title = "Choose Your Explosion"
description = "Three fury-fueled packages designed to match your rage level. All include protective gear, tools, and unlimited smashing."
description = "Three fury-fueled packages designed to match your rage level. All include protective gear, tools, and unlimited smashing. Groups of 4+ get special pricing: €40/person maximum! "
tag = "Pricing"
tagIcon = { Zap }
tagAnimation = "slide-up"
@@ -148,7 +315,7 @@ export default function LandingPage() {
{ text : "Book Package" , onClick : ( ) = > openBookingModal ( 'basic' ) } ,
] ,
features : [
"30 minutes of pure destruction" , "All safety gear included" , "Glass and ceramics" , "Solo or duo session" , "Perfect for first-timers" , "Group discount : €40/person (4+ people)"
"30 minutes of pure destruction" , "All safety gear included" , "Glass and ceramics" , "Solo or duo session" , "Perfect for first-timers" , "Group price : €40/person (max 4 people)"
] ,
} ,
{
@@ -157,7 +324,7 @@ export default function LandingPage() {
{ text : "Book Now" , onClick : ( ) = > openBookingModal ( 'destroyer' ) } ,
] ,
features : [
"60 minutes of maximum chaos" , "Premium safety equipment" , "Glass, ceramics & electronics" , "Doubles or small group" , "Most items to smash" , "Best value experience" , "Group discount : €40/person (4+ people)"
"60 minutes of maximum chaos" , "Premium safety equipment" , "Glass, ceramics & electronics" , "Doubles or small group" , "Most items to smash" , "Best value experience" , "Group price : €40/person (max 4 people)"
] ,
} ,
{
@@ -166,7 +333,7 @@ export default function LandingPage() {
{ text : "Reserve Elite" , onClick : ( ) = > openBookingModal ( 'elite' ) } ,
] ,
features : [
"90 minutes unlimited destruction" , "VIP treatment & priorities" , "All destruction categories" , "Small group packages" , "Premium room setup" , "Professional photos included" , "Group discount : €40/person (4+ people)"
"90 minutes unlimited destruction" , "VIP treatment & priorities" , "All destruction categories" , "Small group packages" , "Premium room setup" , "Professional photos included" , "Group price : €40/person (max 4 people)"
] ,
} ,
] }
@@ -210,7 +377,7 @@ export default function LandingPage() {
id : "2" , title : "Is it really safe?" , content : "Absolutely. All participants receive thorough safety briefing, professional-grade protective equipment, and constant supervision. Our trained staff ensures a safe, controlled environment for pure adrenaline release."
} ,
{
id : "3" , title : "How many people can join?" , content : "Sessions are designed for solo destructors up to small groups. Basic packages work great for 1-2 people. Destroyer and Elite packages support small groups. Contact us for group booking option s."
id : "3" , title : "How many people can join?" , content : "Maximum group size is 4 people per session. If your group is larger than 4 people, please contact us at bookings@rageroomvienna.local to arrange custom group bookings."
} ,
{
id : "4" , title : "What's included in pricing?" , content : "All packages include full protective gear, safety briefing, tools, smashable items (glass, ceramics, electronics), and supervised session time. Photos available in Elite package."
@@ -219,7 +386,7 @@ export default function LandingPage() {
id : "5" , title : "Are there age restrictions?" , content : "Minimum age is 16 years old (with parental consent). No maximum age limit – we welcome destructors of all ages who can safely handle the experience."
} ,
{
id : "6" , title : "Can I book in advance?" , content : "Yes! Check available time slots on our booking calendar. We're open Thursday 5 PM– 10 PM, Friday 2 PM– 12 AM, Saturday & Sunday 12 PM– 12 AM."
id : "6" , title : "Can I book in advance?" , content : "Yes! Check available time slots on our booking calendar. We're open Thursday 5 PM– 10 PM, Friday 2 PM– 12 AM, Saturday & Sunday 12 PM– 12 AM. Monday– Wednesday are closed. "
} ,
] }
/ >
@@ -248,6 +415,7 @@ export default function LandingPage() {
< p className = "text-foreground font-semibold mb-2" > Rage Room Vienna < / p >
< p className = "text-foreground/70" > Vienna , Austria < / p >
< p className = "text-foreground/70 mt-4" > Open : Thu 5 PM - 10 PM | Fri 2 PM - 12 AM | Sat & Sun 12 PM - 12 AM < / p >
< p className = "text-foreground/70 text-sm mt-2" > Monday - Wednesday : Closed < / p >
< / div >
< / div >
< / div >
@@ -263,7 +431,7 @@ export default function LandingPage() {
description = "Choose your package above and select your time slot on our calendar. Or get in touch with questions – our team is ready to help you plan the perfect destruction session."
buttons = { [
{ text : "Book a Session" , onClick : ( ) = > openBookingModal ( 'destroyer' ) } ,
{ text : "Email Us" , href : "mailto:bookings@rageroomvienna.local?subject=Rage%20Room%20Inquiry " } ,
{ text : "Contact Us" , href : "mailto:bookings@rageroomvienna.local" } ,
] }
buttonAnimation = "slide-up"
background = { { variant : "downward-rays-static" } }
@@ -305,8 +473,8 @@ export default function LandingPage() {
{ showBookingModal && (
< div className = "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4" >
< div className = "bg-white rounded-lg shadow-xl max-w-md w-full" >
< div className = "flex items-center justify-between p-6 border-b" >
< div className = "bg-white rounded-lg shadow-xl max-w-md w-full max-h-[90vh] overflow-y-auto " >
< div className = "flex items-center justify-between p-6 border-b sticky top-0 bg-white " >
< h2 className = "text-2xl font-bold" > Book Your Session < / h2 >
< button
onClick = { closeBookingModal }
@@ -332,7 +500,7 @@ export default function LandingPage() {
< / div >
< div >
< label className = "block text-sm font-medium mb-2" > Group Size < / label >
< label className = "block text-sm font-medium mb-2" > Group Size ( Max 4 people ) < / label >
< div className = "flex items-center gap-4" >
< button
onClick = { ( ) = > setGroupSize ( Math . max ( 1 , groupSize - 1 ) ) }
@@ -342,15 +510,18 @@ export default function LandingPage() {
< / button >
< span className = "text-lg font-bold min-w-12 text-center" > { groupSize } < / span >
< button
onClick = { ( ) = > setGroupSize ( groupSize + 1 ) }
onClick = { ( ) = > setGroupSize ( Math . min ( 4 , groupSize + 1 ) ) }
className = "px-3 py-2 border rounded hover:bg-gray-100"
>
+
< / button >
< / div >
{ selectedPackage && groupSize > = 4 && (
{ groupSize == = 4 && (
< p className = "text-sm text-green-600 mt-2 font-semibold" > ✓ Group discount applied ! € 40 per person < / p >
) }
{ groupSize > 4 && (
< p className = "text-sm text-red-600 mt-2 font-semibold" > Maximum group size is 4 people . Please contact us for larger groups . < / p >
) }
< / div >
< div >
@@ -361,6 +532,9 @@ export default function LandingPage() {
onChange = { ( e ) = > setSelectedDate ( e . target . value ) }
className = "w-full border rounded px-3 py-2"
/ >
{ getDateWarning ( selectedDate ) && (
< p className = "text-sm text-red-600 mt-2" > { getDateWarning ( selectedDate ) } < / p >
) }
< / div >
< div >
@@ -371,25 +545,57 @@ export default function LandingPage() {
className = "w-full border rounded px-3 py-2"
>
< option value = "" > Select a time < / option >
< option value = "17:00" > 5 :00 PM < / option >
< option value = "18:00" > 6 :00 PM < / option >
< option value = "19:00" > 7 :00 PM < / option >
< option value = "20:00" > 8 :00 PM < / option >
< option value = "21:00" > 9 :00 PM < / option >
{ getAvailableTimes ( selectedDate ) . map ( time = > (
< option key = { time } value = { time } >
{ isTimeAvailable ( selectedDate , time ) ? time : ` ${ time } (Booked) ` }
< / option >
) ) }
< / select >
{ selectedDate && getAvailableTimes ( selectedDate ) . length === 0 && (
< p className = "text-sm text-red-600 mt-2" > No available times for this date < / p >
) }
< / div >
{ selectedPackage && (
< div >
< label className = "block text-sm font-medium mb-3" > Add - ons ( Optional ) < / label >
< div className = "space-y-2" >
{ addOns . map ( addOn = > (
< label key = { addOn . id } className = "flex items-center gap-3 cursor-pointer" >
< input
type = "checkbox"
checked = { selectedAddOns . includes ( addOn . id ) }
onChange = { ( ) = > handleAddOnChange ( addOn . id ) }
className = "w-4 h-4 rounded border-gray-300"
/ >
< span className = "text-sm" > { addOn . label } < / span >
< / label >
) ) }
< / div >
< / div >
{ selectedPackage && groupSize <= 4 && (
< div className = "bg-gray-50 p-4 rounded-lg" >
< p className = "text-sm text-gray-600" > Price per person < / p >
< p className = "text-2xl font-bold" > € { calculatePrice ( packagePrices [ selectedPackage ] , groupSize ) . toFixed ( 2 ) } < / p >
< p className = "text-sm text-gray-600 mt-1" > Total : € { ( calculatePrice ( packagePrice s[ selectedPackage ] , groupSize ) * groupSize ) . toFixed ( 2 ) } < / p >
{ selectedAddOn s. length > 0 && (
< div className = "mt-3 pt-3 border-t text-sm" >
< p className = "text-gray-600" > Add - ons : € { selectedAddOns . reduce ( ( sum , id ) = > sum + ( addOns . find ( a = > a . id === id ) ? . price || 0 ) , 0 ) . toFixed ( 2 ) } < / p >
< / div >
) }
< p className = "text-sm text-gray-600 mt-2" > Total : € { calculateTotalPrice ( ) . toFixed ( 2 ) } < / p >
< / div >
) }
{ groupSize > 4 && (
< div className = "bg-red-50 p-4 rounded-lg border border-red-200" >
< p className = "text-sm text-red-800 font-semibold" > Group size exceeds maximum < / p >
< p className = "text-sm text-red-700 mt-1" > For groups larger than 4 people , please contact us at bookings @rageroomvienna . local < / p >
< / div >
) }
< button
onClick = { handleBooking }
disabled = { ! selectedPackage || ! selectedDate || ! selectedTime }
disabled = { ! selectedPackage || ! selectedDate || ! selectedTime || groupSize > 4 }
className = "w-full bg-blue-600 text-white py-3 rounded-lg font-semibold hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
>
Confirm Booking
@@ -398,6 +604,63 @@ export default function LandingPage() {
< / div >
< / div >
) }
{ showSuccessPopup && lastBooking && (
< div className = "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4" >
< div className = "bg-white rounded-lg shadow-xl max-w-md w-full p-8 text-center" >
< div className = "mb-4 flex justify-center" >
< div className = "bg-green-100 rounded-full p-3" >
< CheckCircle size = { 40 } className = "text-green-600" / >
< / div >
< / div >
< h2 className = "text-2xl font-bold mb-2 text-green-600" > Booking Confirmed ! < / h2 >
< p className = "text-gray-600 mb-6" > Your rage session is booked and ready to go . < / p >
< div className = "bg-gray-50 rounded-lg p-4 text-left space-y-2 mb-6" >
< div className = "flex justify-between" >
< span className = "font-medium" > Package : < / span >
< span className = "capitalize" > { lastBooking . package } < / span >
< / div >
< div className = "flex justify-between" >
< span className = "font-medium" > Date : < / span >
< span > { lastBooking . date } < / span >
< / div >
< div className = "flex justify-between" >
< span className = "font-medium" > Time : < / span >
< span > { lastBooking . time } < / span >
< / div >
< div className = "flex justify-between" >
< span className = "font-medium" > Group Size : < / span >
< span > { lastBooking . groupSize } person ( s ) < / span >
< / div >
{ lastBooking . addOns . length > 0 && (
< div className = "border-t pt-2 mt-2" >
< p className = "font-medium mb-2" > Add - ons : < / p >
{ lastBooking . addOns . map ( ( addOn : any ) = > (
< div key = { addOn . id } className = "flex justify-between text-sm" >
< span > { addOn . label } < / span >
< span > € { addOn . price . toFixed ( 2 ) } < / span >
< / div >
) ) }
< / div >
) }
< div className = "border-t pt-2 mt-2 flex justify-between font-bold" >
< span > Total : < / span >
< span > € { lastBooking . price . toFixed ( 2 ) } < / span >
< / div >
< / div >
< p className = "text-sm text-gray-600 mb-6" > A confirmation email has been sent to your registered email address . Get ready to destroy ! < / p >
< button
onClick = { ( ) = > setShowSuccessPopup ( false ) }
className = "w-full bg-green-600 text-white py-3 rounded-lg font-semibold hover:bg-green-700"
>
Close
< / button >
< / div >
< / div >
) }
< / ThemeProvider >
) ;
}