Scroll Issue
Scroll Issue
import 'react-toastify/dist/ReactToastify.css';
import res1 from '../assets/res1.jpeg';
import res2 from '../assets/res2.jpeg';
import res3 from '../assets/res3.jpeg';
import res4 from '../assets/res4.jpeg';
import res5 from '../assets/res5.jpeg';
import res6 from '../assets/res6.jpeg';
import res7 from '../assets/res7.jpeg';
import "./style.css";
import axios from "axios";
import { LoaderOverlay } from './loader';
import { restList } from "../../data/restList";
const stripePromise =
loadStripe('pk_test_51PQGnaDSTeiybpPZu7tYvyIpPsAlQREBAATsf0cupUqKJhNIuzx1TDoiqEyKK6
B6dgDSt7nKiQu2S7JNxBn3FR8o00DRHLPKD0'); // Replace with your actual Stripe public
key
function SearchIcon(props) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
</svg>
);
}
function ChevronDownIcon(props) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m6 9 6 6 6-6" />
</svg>
);
}
window.scrollTo({
top: offsetPosition,
behavior: 'smooth',
});
}
};
// });
useEffect(() => {
useEffect(() => {
if (selectedDate) {
setTimeout(() => {
scrollToSection(guestRef);
}, 50);
}
}, [selectedDate]);
// useEffect(() => {
// if (selectedGuests) {
// scrollToSection(timeRef); // Scroll to the time selection section after
it's rendered
// }
// }, [selectedGuests]);
const openModal = () => setIsOpen(true);
const closeModal = () => setIsOpen(false);
// if (!stripe || !elements) {
// return; // Stripe.js has not loaded yet.
// }
// if (result.error) {
// // Handle errors
// setSetupError(result.error.message);
// } else {
// // Store the payment method ID (result.setupIntent.payment_method) in your
database
// const paymentMethodId = result.setupIntent.payment_method;
setTimeout(() => {
scrollToSection(restaurantsRef); // Scroll back to the restaurant section
setLoginTimeout(timeout);
try {
if (!email || !password) {
toast.error("Please fill in all fields");
return;
}
const response = await
axios.post('https://api.thereservationist.io/api/user/login', {
email,
password,
});
console.log(response,'[[][][][][][][][][');
if (response.data.status === "success") {
const { token, phoneNumber, userId } = response.data.data;
localStorage.setItem("token", token);
localStorage.setItem("userId", userId);
if (phoneNumber) {
localStorage.setItem("phoneNumber", phoneNumber);
setPhoneNumber2(phoneNumber);
}
setLoggedIn(true);
toast.success("Login successful!");
if (!phoneNumber) {
setSendOtpModalOpen(true); // Trigger OTP modal if phone is not verified
}
clearTimeout(timeout);
setIsFirstTime(false);
} else {
toast.error("Login failed. Please check your credentials.");
setLoginModalOpen(true);
}
} catch (error) {
setLoginModalOpen(true);
console.error("Login error:", error);
toast.error("Error during login. Please try again.");
// setLoginModalOpen(true);
setShowLoginForm(true);
} finally {
setLoginInProgress(false);
setLoginLoading(false); // Reset login progress
}
};
// useEffect(() => {
// // Ensure all required data is available before calling createSetupIntent
// if (selectedRestaurant && selectedDate && selectedGuests &&
selectedTimeSlots.length > 0) {
// createPaymentIntent(); // Call the API for creating setup intent
// }
// }, [selectedRestaurant, selectedDate, selectedGuests, selectedTimeSlots]);
setShowModal(true);
} else {
toast.success('Account created');
// await handleReminders(selectedTimeRange);
}
} else {
toast.error('Failed to create setup intent. Please try again.');
}
} catch (error) {
console.error('Error creating setup intent:', error);
toast.error('An error occurred. Please try again.');
} finally {
setLoading(false);
}
};
const handleReminders = async (selectedTimeRange) => {
console.log(selectedTimeRange);
const userId = localStorage.getItem("userId");
const restaurantId = selectedRestaurant._id;
console.log("herererererererererere",)
try {
const nextDay = new Date(selectedDate);
nextDay.setDate(nextDay.getDate() + 1);
const reminderData = {
userId,
restaurantId,
desiredDate: nextDay.toISOString().split('T')[0], // Convert date to
YYYY-MM-DD format
desiredTime: selectedTimeRange,
noOfGuests: selectedGuests,
};
const data=await
axios.post('https://api.thereservationist.io/api/user/remindMe', reminderData);
console.log(data,'api call here');
toast.success(`Reminder set successfully!`);
} catch (error) {
console.error('Error setting reminder:', error);
toast.error(`Failed to set reminder.`);
}
};
useEffect(() => {
const token = localStorage.getItem("token");
const storedPhoneNumber = localStorage.getItem("phoneNumber");
useEffect(() => {
if (phoneNumber2) {
setOtpVerified(true);
setTimeout(() => {
scrollToSection(paymentRef);
}, 50);
// scrollToSection(paymentRef);
}
}, [phoneNumber2]);
router.replace(
`/?restaurant=${slug}&date=${date}&guests=${guests}&start=${start}&end=$
{end}`,
undefined,
{ shallow: true, scroll: false }
);
setSelectedTime(selectedTimeRange);
useEffect(() => {
if (selectedGuests) {
setTimeout(() => {
scrollToSection(timeRef);
}, 50); }
}, [selectedGuests]);
useEffect(() => {
setMounted(true);
const token = localStorage.getItem("token");
setLoggedIn(!!token);
console.log(restList.data, "restList.data");
setAllRestaurants(restList.data)
// axios
// .get("https://api.thereservationist.io/api/admin/restaurants")
// .then((response) => {
// if (response.data.status === "success") {
// console.log(response.data.data,'/////////');
// setAllRestaurants(response.data.data);
// }
// })
// .catch((error) => {
// console.error("Error fetching all restaurants:", error);
// });
}, []);
useEffect(() => {
if (loggedIn && phoneNumber2 && clientSecret) {
setTimeout(() => {
scrollToSection(paymentRef);
}, 50); }
}, [loggedIn, phoneNumber2, clientSecret]);
try {
const response = await axios.get(
`https://api.thereservationist.io/api/user/restaurants/search?name=$
{value}`
);
setSelectedDate(date);
router.replace(`/?restaurant=${slug}&date=${date}&guests=${guests}`, undefined,
{ shallow: true, scroll: false });
setSelectedGuests(guests);
setSelectedTime(null);
setOtpSent(false);
setOtpVerified(false);
};
axios
.post("https://api.thereservationist.io/api/user/initiatePhoneVerification",
{
userId,
phoneNumber: `${countryCode}${phoneNumber}`,
})
.then((response) => {
if (response.data.status === "success") {
setOtpSent(true);
toast.success('OTP sent successfully!');
} else {
toast.error('Failed to send OTP.');
}
})
.catch((error) => {
console.error("Error sending OTP:", error);
toast.error('Error sending OTP. Please try again.');
});
};
// Proceed with the OTP flow if the user is logged in and no valid phone number
exists
if (token && (!storedPhoneNumber || storedPhoneNumber === "undefined")) {
setLoggedIn(true); // Ensure the logged-in state is set
setOtpSent(false); // Reset OTP state to ensure OTP can be sent again
setOtpVerified(false); // Ensure OTP verification starts from scratch
setPhoneNumber2(""); // Reset the stored phone number to trigger the OTP flow
setTimeout(() => {
scrollToSection(phoneRef);
}, 50); // Scroll to the phone verification section
}
}, []);
axios
.post("https://api.thereservationist.io/api/user/verifyPhoneNumber", {
userId,
phoneNumber: `${countryCode}${phoneNumber}`,
verificationCode,
})
.then((response) => {
if (response.data.status === "success") {
setOtpVerified(true);
setPhoneNumber2(phoneNumber);
localStorage.setItem("phoneNumber", phoneNumber);
toast.success('Phone number verified successfully!');
setVerifyOtpModalOpen(false);
setOtpLoading(false);
setLoading(false);
} else {
toast.error('Failed to verify OTP.');
}
})
.catch((error) => {
console.error("Error verifying OTP:", error);
setOtpLoading(false); // Stop loader
toast.error('Error verifying OTP. Please try again.');
});
};
return (
<div className="bg-background">
<ToastContainer />
<header className="sticky top-0 z-10 w-full bg-stone-950 shadow">
<nav className="container mx-auto flex items-center justify-between py-4 px-4
sm:px-6">
{/* Logo */}
<Link href="/" className="text-lg font-bold flex items-center">
<img
src={Logo.src}
alt="Logo"
className="h-8 object-contain"
/>
</Link>
{loggedIn ? (
<div className="relative">
{/* User Icon */}
<button
onClick={toggleUserMenu}
className="flex items-center text-white focus:outline-none"
>
<UserCircleIcon className="h-6 w-6" />
</button>
{/* Dropdown Menu */}
{userMenuOpen && (
<div className="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-
lg z-10">
<Link href="/profile" className="block px-4 py-2 text-sm text-gray-
700 hover:bg-gray-100">
My Profile
</Link>
<Link href="/reservations" className="block px-4 py-2 text-sm text-
gray-700 hover:bg-gray-100">
My Reservations
</Link>
<button
onClick={handleLogout}
className="w-full text-left px-4 py-2 text-sm text-gray-700
hover:bg-gray-100"
>
Logout
</button>
</div>
)}
</div>
) : (
<button
onClick={() => setLoginModalOpen(true)}
className="text-white hover:text-gray-400 px-4 py-2 rounded-md"
>
Login
</button>
)}
</div>
{loggedIn ? (
<>
<Link href="/profile" className="block px-4 py-2 text-sm text-gray-
700 hover:bg-gray-100">
My Profile
</Link>
<Link href="/reservations" className="block px-4 py-2 text-sm text-
gray-700 hover:bg-gray-100">
My Reservations
</Link>
<button
onClick={handleLogout}
className="block w-full text-left px-4 py-2 text-sm text-gray-700
hover:bg-gray-100"
>
Logout
</button>
</>
) : (
<button
onClick={() => setLoginModalOpen(true)}
className="block w-full text-left px-4 py-2 text-sm text-gray-700
hover:bg-gray-100"
>
Login
</button>
)}
</div>
)}
</div>
</nav>
<main>
{/* Loader when searching */}
{searchLoading && (
<div className="flex justify-center my-8">
<LoaderOverlay loading={isLoading} />
</div>
)}
</div>
)}
<header className="relative mb-8">
<div className="absolute inset-0 bg-cover bg-center bg-no-repeat blur-sm
opacity-50" />
<div className="absolute inset-0 bg-gradient-to-t from-background to-
transparent" />
<div
className="relative mx-auto md:w-11/12 lg:w-2/3 flex h-full flex-col
items-center justify-center gap-6 px-2 text-center text-secondary-foreground sm:px-
6 md:gap-6">
<h1 className="text-4xl leading-snug mb-0 mt-5 md:mt-16 font-bold
tracking-tight sm:px-1 md:text-5xl xl:text-6xl sm:leading-snug md:leading-snug
lg:leading-snug xl:leading-snug">
<span className="text-accent-foreground">Book</span> Exclusive
Restaurant <span className="text-white text-nowrap font-extrabold px-2 bg-
resy">Reservations</span>
<span className="bg-gradient-to-tr from-popover-foreground to-slate-
800 text-transparent bg-clip-text">with Ease</span>
</h1>
<p
className="text-md sm:text-lg md:text-xl lg:text-1xl xl:text-2xl max-
w-xs sm:max-w-md md:max-w-xl lg:max-w-2xl"
>Sick and tired of waitlists? The Reservationist will automatically
secure the first open reservation for you! You only pay if we're successful!
</p>
</div>
</header>
{/* Search bar and other dynamic sections start here */}
<div className="flex flex-col gap-0.5 w-full text-center max-w-2xl mx-auto
mt-2 p-1 pt-2 text-secondary-foreground rounded-lg shadow-2xl shadow-stone-500
space-y-0 bg-gradient-to-b from-stone-950 to-stone-600" ref={containerRef}>
<h3 className="text-sm font-semibold text-white">Create Your Booking
Bot 🤖</h3>
</div>
</div>
))}
</div>
)}
</div>
</div>
</div>
</div>
{selectedRestaurant && (
<div ref={restaurantscroll} name="select-date" id="select-date">
<div className="flex flex-col gap-2.5 mt-4 p-6 bg-stone-100 rounded-
md shadow-lg">
<div className="flex bg-gradient-to-bl from-rose-400 to-accent-
foreground m-auto text-xl justify-center align-middle font-extrabold w-10 h-10
text-slate-100 rounded-full leading-loose shadow-stone-700 shadow-sm">2</div>
<h2 className="text-xl font-extrabold m-auto text-transparent bg-
clip-text bg-gradient-to-tr from-stone-950 to-stone-700">Select a Booking Date</h2>
<Calendar
onChange={handleDateSelect}
value={selectedDate}
className="w-full"
calendarType="gregory"
tileClassName={({ date, view }) =>
view === 'month' && date.getDate() === (selectedDate ?
selectedDate.getDate() : null) &&
date.getMonth() === (selectedDate ? selectedDate.getMonth() :
null) &&
date.getFullYear() === (selectedDate ?
selectedDate.getFullYear() : null)
? 'bg-accent-foreground text-white rounded-full'
: undefined
}
/>
</div>
</div>
)}
// Ensure end times are always after the selected start time and include
times up to midnight (12 AM).
if (
!selectedTimeRange.start ||
(currentHour > startHour || (currentHour === startHour && currentMinutes
> startMinutes)) &&
currentHour < 24
) {
return <SelectItem key={i} value={timeLabel}>{timeLabel}</SelectItem>;
}
return null;
})}
</SelectContent>
</Select>
</div>
</div>
{selectedTimeRange.start && selectedTimeRange.end && (
<Button onClick={handleNext} className="mt-4 bg-accent-foreground text-
white">
{!isFirstTime ? "Create Reminder" : "Next"}
</Button>
)}
</div>
</div>
)}
{sendOtpModalOpen && (
<Modal2 onClose={() => setSendOtpModalOpen(false)}>
<div className="p-6">
{/* <button
onClick={() => setSendOtpModalOpen(false)}
className="absolute top-2 right-2 text-gray-500 hover:text-gray-700
focus:outline-none"
>
<XIcon className="h-5 w-5" />
</button> */}
<h2 className="text-xl font-bold mb-4">Send OTP</h2>
<div className="flex gap-2 mb-4">
<Select onValueChange={handleCountryCodeChange} value={countryCode}
className="w-24">
<SelectTrigger className="w-full">
<SelectValue placeholder="Code" />
</SelectTrigger>
<SelectContent>
<SelectItem value="+1">+1 (USA)</SelectItem>
<SelectItem value="+92">+92 (Pakistan)</SelectItem>
<SelectItem value="+44">+44 (UK)</SelectItem>
</SelectContent>
</Select>
<Input
type="text"
placeholder="Phone number"
value={phoneNumber}
onChange={handlePhoneNumberChange}
className="w-full"
/>
</div>
<Button
onClick={() => {
handleSendOtp();
setSendOtpModalOpen(false);
setVerifyOtpModalOpen(true);
}}
disabled={!phoneNumber}
className="w-full"
>
Send OTP
</Button>
<span>
<h6>
<label>
<input type="checkbox" required className="mr-2" />
By entering my mobile number, I agree to receive text messages from The
Reservationist...
</label>
</h6>
</span>
</div>
</Modal2>
)}
{verifyOtpModalOpen && (
<Modal onClose={() => setVerifyOtpModalOpen(false)}>
<div className="p-6">
{/* <button
onClick={() => setVerifyOtpModalOpen(false)}
className="absolute top-2 right-2 text-gray-500 hover:text-gray-700
focus:outline-none"
>
<XIcon className="h-5 w-5" />
</button> */}
<h2 className="text-xl font-bold mb-4">Verify OTP</h2>
<Input
type="text"
placeholder="Enter OTP"
value={verificationCode}
onChange={(e) => setVerificationCode(e.target.value)}
className="w-full mb-4"
/>
<div className="flex justify-between gap-4 mt-4">
<Button
onClick={() => {
setVerifyOtpModalOpen(false);
setSendOtpModalOpen(true);
}}
className="w-1/2 bg-gray-200 text-gray-700 hover:bg-gray-300"
>
Back
</Button>
<Button onClick={handleVerifyOtp} disabled={!verificationCode}
className="w-1/2">
Verify OTP
</Button>
</div>
<div className="flex justify-between mt-4">
<Button variant="link" onClick={() => setSendOtpModalOpen(true)}>
Resend OTP
</Button>
</div>
</div>
</Modal>
)}
{showModal && (
<Modal onClose={() => setShowModal(false)}>
<div className="p-6">
<h2 className="text-xl font-bold mb-4">Confirmation</h2>
<p>You successfully created a reminder!</p>
<div className="mt-4 flex justify-end space-x-2">
<Button onClick={handleNewScan}>Create New Reminder</Button>
<Button onClick={handleNewScan}>Close</Button>
</div>
</div>
</Modal>
)}
{selectedTime && (
<div ref={paymentRef} name="payment-section" id="payment-section">
{loading ? (
<div className="flex justify-center my-8">
<LoaderOverlay loading={true} message="Processing your request..." />
</div>
) : clientSecret ? (
// Stripe part
<Elements stripe={stripePromise}>
<div className="p-6 rounded-lg bg-gray-100 shadow-lg"> {/* Main background
color */}
<CheckoutForm
clientSecret={clientSecret}
userId={localStorage.getItem('userId')}
restaurantId={selectedRestaurant._id}
selectedDate={selectedDate}
selectedGuests={selectedGuests}
selectedTimeSlots={selectedTimeRange}
setPaymentMethod={(paymentMethodId) => {
console.log('Payment Method saved:', paymentMethodId);
setShowModal(true);
setLoading(false);
// Handle further actions here
}}
/>
</div>
</Elements>
) : (
<div className="flex flex-col items-center justify-center my-8">
{loginLoading && (
<div className="flex flex-col items-center justify-center my-8">
<LoaderOverlay loading={true} message="Hang tight! We’re almost done. You’ll
only have to do this once!" />
<p className="mt-4 text-center text-sm text-gray-500">
Please wait a moment while we create your account!
</p>
</div>
)}
</div>
)}
</div>
)}
</div>
</Card>
))}
</div>
{/* {loginModalOpen && (
<Modal showCloseIcon={!openedFromDateSelection}>
<div className="p-6">
{openedFromDateSelection ? null : (
<button
onClick={() => setLoginModalOpen(false)}
className="absolute top-2 right-2 text-gray-500 hover:text-gray-700
focus:outline-none"
>
<XIcon className="h-5 w-5" />
</button>
)}
<img
src={resyLogo.src}
alt="Resy Logo"
width={100}
className="w-32 h-auto mb-4 m-auto"
/>
<Input
type="email"
placeholder="Email"
value={email} // Controlled input
onChange={(e) => setEmail(e.target.value)} // Update email state
className="w-full mb-4"
/>
<div className="relative">
<Input
type={isPasswordVisible ? "text" : "password"}
placeholder="Password"
value={password} // Controlled input
onChange={(e) => setPassword(e.target.value)} // Update password state
className="w-full mb-4"
/>
<button
type="button"
onClick={togglePasswordVisibility} // Toggle visibility on click
className="absolute right-3 top-3"
>
{isPasswordVisible ? (
<EyeOffIcon className="h-5 w-5 text-gray-500" />
) : (
<EyeIcon className="h-5 w-5 text-gray-500" />
)}
</button>
</div>
<Button
onClick={() => handleLoginSuccess()}
className="w-full"
>
Login to continue
</Button>
</div>
</Modal>
)} */}
</section>
<section
className="flex flex-col items-center justify-center px-4 my-12 sm:px-6 md:my-16
lg:my-20 mb-16" // Add `mb-16` for extra bottom margin
ref={pricingRef}
>
<div className="mx-auto max-w-3xl text-center">
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl">
<button
onClick={() => setTimeout(() => {
scrollToSection(pricingRef);
}, 50)}
className="text-muted-foreground hover:text-foreground px-4 py-2 rounded-md"
style={{ color: "Black" }}
>
Pricing
</button>
</h2>
<div className="mt-3 flex flex-col items-center">
<div className="text-5xl font-extrabold text-accent-foreground">$</div>
<p className="mt-3 text-lg text-muted-foreground sm:mt-4 max-w-2xl">
The Reservationist is 100% risk-free! We won’t charge you unless we
successfully secure a reservation for you and your party. For as low as $10
per person, we can get you into the restaurants you’ve long sought to
enjoy!
</p>
</div>
</div>
</section>
If you need to modify your reservation, please log into your Resy account, cancel
your reservation, and try booking again using The Reservationist. Please note that
you will not be refunded by The Reservationist if you need to modify your
reservation. </CollapsibleContent>
</Collapsible>
<Collapsible>
<CollapsibleTrigger className="flex w-full items-center justify-
between rounded-lg bg-muted px-6 py-4 font-medium transition-colors hover:bg-
muted/80">
<span>What happens if The Reservationist is not able to book a
table for me?</span>
<ChevronDownIcon className="h-5 w-5 transition-transform" />
</CollapsibleTrigger>
<CollapsibleContent className="px-6 py-4 text-muted-foreground">
That can happen, albeit rarely. If we are not able to secure your
reservation, just try again on another date! You are only charged if we confirm
your table! </CollapsibleContent>
</Collapsible>
<Collapsible>
<CollapsibleTrigger className="flex w-full items-center justify-
between rounded-lg bg-muted px-6 py-4 font-medium transition-colors hover:bg-
muted/80">
<span>How much does the Reservationist cost?</span>
<ChevronDownIcon className="h-5 w-5 transition-transform" />
</CollapsibleTrigger>
<CollapsibleContent className="px-6 py-4 text-muted-foreground">
At only $10 per person, The Reservationist is not only the fastest,
but the cheapest way to secure your exclusive reservations. If you think about it,
that’s only a small fraction of what your total bill will be! </CollapsibleContent>
</Collapsible>
<Collapsible>
<CollapsibleTrigger className="flex w-full items-center justify-
between rounded-lg bg-muted px-6 py-4 font-medium transition-colors hover:bg-
muted/80">
<span>Why was I charged by The Reservationist and RESY?</span>
<ChevronDownIcon className="h-5 w-5 transition-transform" />
</CollapsibleTrigger>
<CollapsibleContent className="px-6 py-4 text-muted-foreground">
Sometimes Restaurants require you to put a deposit on your
reservation. In this case, you will be charged $10 per person by The Reservationist
and whatever the deposit is by the restaurant. Please check Resy’s terms and
policies for any additional questions related to Resy deposits.
</CollapsibleContent>
</Collapsible>
<Collapsible>
<CollapsibleTrigger className="flex w-full items-center justify-
between rounded-lg bg-muted px-6 py-4 font-medium transition-colors hover:bg-
muted/80">
<span>Who do I reach out to for more questions?</span>
<ChevronDownIcon className="h-5 w-5 transition-transform" />
</CollapsibleTrigger>
<CollapsibleContent className="px-6 py-4 text-muted-foreground">
Please reach out to us by sending an email to
SOS@thereservationist.com.
</CollapsibleContent>
</Collapsible>
</div>
</section>
<Footer />
</main>
</div>
);
}