mirror of
https://github.com/velocitatem/PHANTOM.git
synced 2026-05-31 08:33:36 +00:00
chore: clean up product display in hotel and cleaner interfacing
This commit is contained in:
@@ -290,8 +290,14 @@ async def get_products(
|
|||||||
query = supabase.table(table).select('*')
|
query = supabase.table(table).select('*')
|
||||||
|
|
||||||
# filter by exact date_index if provided
|
# filter by exact date_index if provided
|
||||||
|
# dateIndex from frontend is days from today, convert to days since epoch
|
||||||
if dateIndex is not None:
|
if dateIndex is not None:
|
||||||
query = query.eq('date_index', dateIndex)
|
from datetime import datetime
|
||||||
|
epoch = datetime(1970, 1, 1)
|
||||||
|
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
today_index = int((today - epoch).total_seconds() / 86400)
|
||||||
|
actual_date_index = today_index + dateIndex
|
||||||
|
query = query.eq('date_index', actual_date_index)
|
||||||
|
|
||||||
response = query.execute()
|
response = query.execute()
|
||||||
results = response.data
|
results = response.data
|
||||||
|
|||||||
@@ -96,7 +96,10 @@ export default function CartPage() {
|
|||||||
<span className="text-3xl font-bold">${total.toFixed(2)}</span>
|
<span className="text-3xl font-bold">${total.toFixed(2)}</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => dispatchInteraction('checkout_start', undefined, { total, itemCount })}
|
onClick={() => {
|
||||||
|
dispatchInteraction('checkout_start', undefined, { total, itemCount });
|
||||||
|
window.location.href = '/checkout';
|
||||||
|
}}
|
||||||
className="w-full py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors"
|
className="w-full py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors"
|
||||||
>
|
>
|
||||||
Proceed to Checkout
|
Proceed to Checkout
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const AmenityIcon = ({ name }: { name: string }) => {
|
|||||||
breakfast: 'Breakfast',
|
breakfast: 'Breakfast',
|
||||||
spa: 'Spa',
|
spa: 'Spa',
|
||||||
};
|
};
|
||||||
return <span className="feature-tag">{iconMap[name.toLowerCase()] || name}</span>;
|
return <span className="feature-tag">{iconMap[name.toLowerCase()] || name.replaceAll("_", " ")}</span>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function HotelCard({ hotel }: { hotel: Hotel }) {
|
export default function HotelCard({ hotel }: { hotel: Hotel }) {
|
||||||
@@ -72,7 +72,6 @@ export default function HotelCard({ hotel }: { hotel: Hotel }) {
|
|||||||
|
|
||||||
<div className="hotel-info">
|
<div className="hotel-info">
|
||||||
<h3 ref={titleRef} className="hotel-name">{hotel.name}</h3>
|
<h3 ref={titleRef} className="hotel-name">{hotel.name}</h3>
|
||||||
<div className="hotel-location text-sm mb-2">{hotel.roomType}</div>
|
|
||||||
<div className="text-sm text-[var(--text-secondary)] mb-2">
|
<div className="text-sm text-[var(--text-secondary)] mb-2">
|
||||||
{hotel.checkIn} - {hotel.checkOut}
|
{hotel.checkIn} - {hotel.checkOut}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ export default function HotelDetails({ product, onAddToCart, addedToCart }: Hote
|
|||||||
<div className="flex-1 flex flex-col">
|
<div className="flex-1 flex flex-col">
|
||||||
<div className="border-b pb-6 mb-6">
|
<div className="border-b pb-6 mb-6">
|
||||||
<h1 className="text-4xl font-bold text-gray-900 mb-2">{product.name}</h1>
|
<h1 className="text-4xl font-bold text-gray-900 mb-2">{product.name}</h1>
|
||||||
<p className="text-xl text-gray-500">{product.roomType}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-8 mb-8">
|
<div className="grid grid-cols-2 gap-8 mb-8">
|
||||||
@@ -86,7 +85,7 @@ export default function HotelDetails({ product, onAddToCart, addedToCart }: Hote
|
|||||||
<div className="flex flex-wrap gap-3">
|
<div className="flex flex-wrap gap-3">
|
||||||
{product.amenities.map(a => (
|
{product.amenities.map(a => (
|
||||||
<span key={a} className="px-3 py-1.5 bg-gray-100 text-gray-700 rounded-md text-sm font-medium">
|
<span key={a} className="px-3 py-1.5 bg-gray-100 text-gray-700 rounded-md text-sm font-medium">
|
||||||
{a}
|
{a.replaceAll('_', ' ')}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -98,11 +97,6 @@ export default function HotelDetails({ product, onAddToCart, addedToCart }: Hote
|
|||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<PriceDisplay productId={product.id} className="!text-2xl" />
|
<PriceDisplay productId={product.id} className="!text-2xl" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-500 mb-1">Total for {product.nights} night{product.nights > 1 ? 's' : ''}</p>
|
|
||||||
<div className="flex items-baseline gap-2">
|
|
||||||
<PriceTotalDisplay productId={product.id} nights={product.nights} />
|
|
||||||
<span className="text-gray-500">/ {product.nights} nights</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,7 +1,29 @@
|
|||||||
import { InputHTMLAttributes } from 'react';
|
import { InputHTMLAttributes, useMemo } from 'react';
|
||||||
|
|
||||||
interface DateInpProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {}
|
interface DateInpProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {}
|
||||||
|
|
||||||
export default function DateInput({ className = '', ...props }: DateInpProps) {
|
export default function DateInput({ className = '', ...props }: DateInpProps) {
|
||||||
return <input type="date" className={`input-field ${className}`.trim()} {...props} />;
|
const { minDate, maxDate } = useMemo(() => {
|
||||||
|
const today = new Date();
|
||||||
|
const tomorrow = new Date(today);
|
||||||
|
tomorrow.setDate(today.getDate() + 1);
|
||||||
|
|
||||||
|
const tenDaysOut = new Date(tomorrow);
|
||||||
|
tenDaysOut.setDate(tomorrow.getDate() + 9); // tomorrow + 9 = 10 days total
|
||||||
|
|
||||||
|
return {
|
||||||
|
minDate: tomorrow.toISOString().split('T')[0],
|
||||||
|
maxDate: tenDaysOut.toISOString().split('T')[0]
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
className={`input-field ${className}`.trim()}
|
||||||
|
min={minDate}
|
||||||
|
max={maxDate}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const NavLink = ({ href, children }: { href: string; children: React.ReactNode }
|
|||||||
href={href}
|
href={href}
|
||||||
className={`px-4 py-2 rounded-md transition-colors ${
|
className={`px-4 py-2 rounded-md transition-colors ${
|
||||||
isActive
|
isActive
|
||||||
? 'bg-[var(--accent-primary)] text-white font-semibold'
|
? 'bg-[var(--accent-primary)] font-semibold'
|
||||||
: 'hover:bg-[var(--accent-primary-light)] text-[var(--text-primary)]'
|
: 'hover:bg-[var(--accent-primary-light)] text-[var(--text-primary)]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -37,9 +37,7 @@ export default function Navigation() {
|
|||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
<NavLink href="/">Home</NavLink>
|
<NavLink href="/">Home</NavLink>
|
||||||
<NavLink href="/products">Products</NavLink>
|
<NavLink href="/products">Products</NavLink>
|
||||||
<NavLink href="/search">Search</NavLink>
|
|
||||||
<NavLink href="/cart">Cart</NavLink>
|
<NavLink href="/cart">Cart</NavLink>
|
||||||
<NavLink href="/checkout">Checkout</NavLink>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,16 +29,35 @@ const EPOCH = new Date(0);
|
|||||||
|
|
||||||
export const transformProduct = (p: HotelProduct): Hotel => {
|
export const transformProduct = (p: HotelProduct): Hotel => {
|
||||||
const { id, room_type, date_index, metadata } = p;
|
const { id, room_type, date_index, metadata } = p;
|
||||||
const checkIn = new Date(EPOCH.getTime() + date_index * 86400000);
|
|
||||||
|
// DB stores date_index as days since epoch
|
||||||
|
// but if value is small (<1000), treat as days from today for backward compat
|
||||||
|
let checkIn: Date;
|
||||||
|
if (date_index < 1000) {
|
||||||
|
// legacy: treat as offset from today
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
checkIn = new Date(today.getTime() + date_index * 86400000);
|
||||||
|
} else {
|
||||||
|
// proper: days since epoch
|
||||||
|
checkIn = new Date(EPOCH.getTime() + date_index * 86400000);
|
||||||
|
}
|
||||||
|
|
||||||
const nights = 1;
|
const nights = 1;
|
||||||
const checkOut = new Date(checkIn.getTime() + nights * 86400000);
|
const checkOut = new Date(checkIn.getTime() + nights * 86400000);
|
||||||
|
|
||||||
|
const formatOpts: Intl.DateTimeFormatOptions = {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
year: checkIn.getFullYear() !== new Date().getFullYear() ? 'numeric' : undefined
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name: metadata?.name || room_type,
|
name: metadata?.name || room_type,
|
||||||
roomType: room_type,
|
roomType: room_type,
|
||||||
checkIn: checkIn.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
|
checkIn: checkIn.toLocaleDateString('en-US', formatOpts),
|
||||||
checkOut: checkOut.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
|
checkOut: checkOut.toLocaleDateString('en-US', formatOpts),
|
||||||
dateIndex: date_index,
|
dateIndex: date_index,
|
||||||
amenities: metadata?.amenities || [],
|
amenities: metadata?.amenities || [],
|
||||||
pricePerNight: metadata?.base_price || 100,
|
pricePerNight: metadata?.base_price || 100,
|
||||||
|
|||||||
Reference in New Issue
Block a user