From 8c24cf88c2f67a99983667aa4086cd98cd31c3ad Mon Sep 17 00:00:00 2001 From: bender Date: Thu, 5 Mar 2026 22:54:42 +0000 Subject: [PATCH 1/3] Add src/app/api/bookings/route.ts --- src/app/api/bookings/route.ts | 58 +++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/app/api/bookings/route.ts diff --git a/src/app/api/bookings/route.ts b/src/app/api/bookings/route.ts new file mode 100644 index 0000000..f96cfcb --- /dev/null +++ b/src/app/api/bookings/route.ts @@ -0,0 +1,58 @@ +import { NextRequest, NextResponse } from 'next/server'; + +interface BookingData { + package: string; + groupSize: number; + date: string; + time: string; + price: number; + addOns: Array<{ id: string; label?: string; price?: number }>; + timestamp: string; +} + +// In-memory store for demo purposes +const bookings: BookingData[] = []; + +export async function POST(request: NextRequest) { + try { + const bookingData: BookingData = await request.json(); + + // Validate required fields + if (!bookingData.package || !bookingData.date || !bookingData.time || !bookingData.groupSize) { + return NextResponse.json( + { error: 'Missing required booking fields' }, + { status: 400 } + ); + } + + // In production, save to actual database + // For demo, store in memory + bookings.push(bookingData); + + console.log('Booking saved:', bookingData); + + return NextResponse.json( + { + success: true, + message: 'Booking confirmed successfully', + bookingId: `BOOKING-${Date.now()}`, + booking: bookingData, + }, + { status: 201 } + ); + } catch (error) { + console.error('Booking error:', error); + return NextResponse.json( + { error: 'Failed to process booking' }, + { status: 500 } + ); + } +} + +export async function GET() { + // Return all bookings (for admin/demo purposes) + return NextResponse.json({ + bookings, + totalBookings: bookings.length, + }); +} -- 2.49.1 From c185064a1e2ba57c05cffa3a84cbc3ab9321fd37 Mon Sep 17 00:00:00 2001 From: bender Date: Thu, 5 Mar 2026 22:54:42 +0000 Subject: [PATCH 2/3] Update src/app/layout.tsx --- src/app/layout.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 615db34..6e7bbe5 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -23,11 +23,13 @@ const poppins = Poppins({ export const metadata: Metadata = { title: "Rage Room Vienna | Smash Therapy & Destruction Experience", description: "Book your ultimate destruction experience at Rage Room Vienna. Premium smash therapy packages with protective gear, unlimited items to destroy, and pure adrenaline rush.", keywords: "rage room Vienna, destruction therapy, stress relief, smash experience, adrenaline rush, team building", metadataBase: new URL("https://www.rageroomvienna.at"), alternates: { - canonical: "https://www.rageroomvienna.at"}, + canonical: "https://www.rageroomvienna.at" + }, openGraph: { title: "Rage Room Vienna | Destroy Everything", description: "Experience ultimate stress relief at Vienna's premier rage room. Book your chaos session today.", url: "https://www.rageroomvienna.at", siteName: "Rage Room Vienna", type: "website", images: [ { - url: "http://img.b2bpic.net/free-photo/person-suffering-from-technology-addiction-cybersickness_23-2151552653.jpg", alt: "Rage Room destruction experience"}, + url: "http://img.b2bpic.net/free-photo/person-suffering-from-technology-addiction-cybersickness_23-2151552653.jpg", alt: "Rage Room destruction experience" + }, ], }, twitter: { -- 2.49.1 From f77990cfbcd07b1290d6a7b7590981462c2dbe60 Mon Sep 17 00:00:00 2001 From: bender Date: Thu, 5 Mar 2026 22:54:43 +0000 Subject: [PATCH 3/3] Update src/app/page.tsx --- src/app/page.tsx | 257 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 214 insertions(+), 43 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 5277cca..44de95d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -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(null); const [groupSize, setGroupSize] = useState(1); const [selectedDate, setSelectedDate] = useState(''); const [selectedTime, setSelectedTime] = useState(''); + const [selectedAddOns, setSelectedAddOns] = useState([]); + const [bookedSlots, setBookedSlots] = useState([ + '2025-01-16T17:00', + '2025-01-16T19:00', + '2025-01-17T14:00', + '2025-01-18T15:00', + '2025-01-18T20:00', + ]); + const [lastBooking, setLastBooking] = useState(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,23 +61,95 @@ export default function LandingPage() { setGroupSize(1); setSelectedDate(''); setSelectedTime(''); + setSelectedAddOns([]); }; - const handleBooking = () => { + const handleAddOnChange = (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); @@ -98,28 +187,26 @@ export default function LandingPage() { return []; } + let availableTimes = []; if (dayOfWeek === 4) { - return allTimes.filter(time => { + availableTimes = allTimes.filter(time => { const [hours] = time.split(':').map(Number); return hours >= 17 && hours < 22; }); - } - - if (dayOfWeek === 5) { - return allTimes.filter(time => { + } else if (dayOfWeek === 5) { + availableTimes = allTimes.filter(time => { const [hours] = time.split(':').map(Number); return hours >= 14 && hours < 24; }); - } - - if (dayOfWeek === 6 || dayOfWeek === 0) { - return allTimes.filter(time => { + } else if (dayOfWeek === 6 || dayOfWeek === 0) { + availableTimes = allTimes.filter(time => { const [hours] = time.split(':').map(Number); return hours >= 12 && hours < 24; }); } - return []; + // Filter out booked slots + return availableTimes.filter(time => !bookedSlots.includes(`${date}T${time}`)); }; const getDateWarning = (date: string): string | null => { @@ -176,16 +263,20 @@ export default function LandingPage() { testimonials={[ { name: "Marcus K.", handle: "Stressed Executive", testimonial: "Best stress relief I've ever experienced. Walked in angry, left smiling. Booking again!", rating: 5, - imageSrc: "http://img.b2bpic.net/free-photo/close-up-portrait-young-handsome-successful-man_1163-5475.jpg?_wi=1"}, + imageSrc: "http://img.b2bpic.net/free-photo/close-up-portrait-young-handsome-successful-man_1163-5475.jpg?_wi=1" + }, { name: "Lisa M.", handle: "Corporate Manager", testimonial: "The team loved it! We came as a group and it was the perfect team building activity. Absolutely cathartic.", rating: 5, - imageSrc: "http://img.b2bpic.net/free-photo/close-up-portrait-young-handsome-successful-man_1163-5475.jpg?_wi=2"}, + imageSrc: "http://img.b2bpic.net/free-photo/close-up-portrait-young-handsome-successful-man_1163-5475.jpg?_wi=2" + }, { name: "Daniel R.", handle: "Fitness Enthusiast", testimonial: "Incredible workout disguised as entertainment. The adrenaline rush is unmatched. Worth every euro.", rating: 5, - imageSrc: "http://img.b2bpic.net/free-photo/close-up-portrait-young-handsome-successful-man_1163-5475.jpg?_wi=3"}, + imageSrc: "http://img.b2bpic.net/free-photo/close-up-portrait-young-handsome-successful-man_1163-5475.jpg?_wi=3" + }, { name: "Sophie V.", handle: "Break-Up Survivor", testimonial: "Exactly what I needed. Therapeutic, fun, and exhilarating. Five stars all the way!", rating: 5, - imageSrc: "http://img.b2bpic.net/free-photo/close-up-portrait-young-handsome-successful-man_1163-5475.jpg?_wi=4"}, + imageSrc: "http://img.b2bpic.net/free-photo/close-up-portrait-young-handsome-successful-man_1163-5475.jpg?_wi=4" + }, ]} testimonialRotationInterval={5000} imageSrc="http://img.b2bpic.net/free-photo/person-suffering-from-technology-addiction-cybersickness_23-2151552653.jpg?_wi=1" @@ -211,19 +302,9 @@ export default function LandingPage() {
-
-
-
-

Group Discount Available

-

4 people = €40/person (Maximum)

-

Perfect for team building and group experiences

-

Groups larger than 4 people: Contact us for custom bookings

-
-
-
openBookingModal('basic') }, ], features: [ - "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)"], + "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)" + ], }, { id: "destroyer", badge: "Most Popular", badgeIcon: Sparkles, @@ -242,7 +324,8 @@ 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 price: €40/person (max 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)" + ], }, { id: "elite", badge: "Ultimate", badgeIcon: Crown, @@ -250,7 +333,8 @@ 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 price: €40/person (max 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)" + ], }, ]} animationType="slide-up" @@ -287,17 +371,23 @@ export default function LandingPage() { useInvertedBackground={false} faqs={[ { - id: "1", title: "What should I wear?", content: "Wear comfortable, sturdy clothing and closed-toe shoes. We provide full protective gear including helmet, gloves, and safety vest. No special attire needed – we'll gear you up for maximum safety."}, + id: "1", title: "What should I wear?", content: "Wear comfortable, sturdy clothing and closed-toe shoes. We provide full protective gear including helmet, gloves, and safety vest. No special attire needed – we'll gear you up for maximum safety." + }, { - 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: "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: "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: "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."}, + 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." + }, { - 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: "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. Monday–Wednesday are closed."}, + 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." + }, ]} />
@@ -383,8 +473,8 @@ export default function LandingPage() { {showBookingModal && (
-
-
+
+

Book Your Session

+
+ +
+ {addOns.map(addOn => ( + + ))} +
+
+ {selectedPackage && groupSize <= 4 && (

Price per person

€{calculatePrice(packagePrices[selectedPackage], groupSize).toFixed(2)}

-

Total: €{(calculatePrice(packagePrices[selectedPackage], groupSize) * groupSize).toFixed(2)}

+ {selectedAddOns.length > 0 && ( +
+

Add-ons: €{selectedAddOns.reduce((sum, id) => sum + (addOns.find(a => a.id === id)?.price || 0), 0).toFixed(2)}

+
+ )} +

Total: €{calculateTotalPrice().toFixed(2)}

)} @@ -490,6 +604,63 @@ export default function LandingPage() {
)} + + {showSuccessPopup && lastBooking && ( +
+
+
+
+ +
+
+

Booking Confirmed!

+

Your rage session is booked and ready to go.

+ +
+
+ Package: + {lastBooking.package} +
+
+ Date: + {lastBooking.date} +
+
+ Time: + {lastBooking.time} +
+
+ Group Size: + {lastBooking.groupSize} person(s) +
+ {lastBooking.addOns.length > 0 && ( +
+

Add-ons:

+ {lastBooking.addOns.map((addOn: any) => ( +
+ {addOn.label} + €{addOn.price.toFixed(2)} +
+ ))} +
+ )} +
+ Total: + €{lastBooking.price.toFixed(2)} +
+
+ +

A confirmation email has been sent to your registered email address. Get ready to destroy!

+ + +
+
+ )} ); } -- 2.49.1