Merge version_6 into main #12

Merged
bender merged 3 commits from version_6 into main 2026-03-05 22:54:47 +00:00
3 changed files with 276 additions and 45 deletions

View File

@@ -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,
});
}

View File

@@ -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: {

View File

@@ -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,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() {
</div>
<div id="pricing" data-section="pricing">
<div className="w-full bg-gradient-to-b from-background to-background py-12 md:py-16 flex items-center justify-center">
<div className="w-full max-w-3xl px-6 md:px-8 text-center">
<div className="mb-8 md:mb-12">
<h3 className="text-2xl md:text-3xl font-bold text-foreground mb-3">Group Discount Available</h3>
<p className="text-lg md:text-2xl font-semibold text-primary-cta">4 people = 40/person (Maximum)</p>
<p className="text-sm md:text-base text-foreground/70 mt-2">Perfect for team building and group experiences</p>
<p className="text-xs md:text-sm text-foreground/60 mt-3 font-medium">Groups larger than 4 people: Contact us for custom bookings</p>
</div>
</div>
</div>
<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"
@@ -234,7 +315,8 @@ 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 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 PM10 PM, Friday 2 PM12 AM, Saturday & Sunday 12 PM12 AM. MondayWednesday 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 PM10 PM, Friday 2 PM12 AM, Saturday & Sunday 12 PM12 AM. MondayWednesday are closed."
},
]}
/>
</div>
@@ -383,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}
@@ -456,7 +546,9 @@ export default function LandingPage() {
>
<option value="">Select a time</option>
{getAvailableTimes(selectedDate).map(time => (
<option key={time} value={time}>{time}</option>
<option key={time} value={time}>
{isTimeAvailable(selectedDate, time) ? time : `${time} (Booked)`}
</option>
))}
</select>
{selectedDate && getAvailableTimes(selectedDate).length === 0 && (
@@ -464,11 +556,33 @@ export default function LandingPage() {
)}
</div>
<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(packagePrices[selectedPackage], groupSize) * groupSize).toFixed(2)}</p>
{selectedAddOns.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>
)}
@@ -490,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>
);
}