Fix option card readability and image loading
This commit is contained in:
File diff suppressed because one or more lines are too long
18
client/dist/assets/index-OJVYK787.js
vendored
18
client/dist/assets/index-OJVYK787.js
vendored
File diff suppressed because one or more lines are too long
18
client/dist/assets/index-P6j6uqrQ.js
vendored
Normal file
18
client/dist/assets/index-P6j6uqrQ.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
client/dist/index.html
vendored
4
client/dist/index.html
vendored
@@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Cabo Bachelor Party</title>
|
||||
<script type="module" crossorigin src="/assets/index-OJVYK787.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DKrFtyUx.css">
|
||||
<script type="module" crossorigin src="/assets/index-P6j6uqrQ.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-B5xoFPr6.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -24,7 +24,6 @@ const CAT_EMOJI = {
|
||||
itinerary: '🗺️',
|
||||
budget: '💸',
|
||||
}
|
||||
|
||||
function normalizeSourceKey(value) {
|
||||
return String(value || '')
|
||||
.trim()
|
||||
@@ -83,6 +82,12 @@ function cx(...classes) {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
function getOptionImageUrl(option) {
|
||||
if (option.imageUrl) return option.imageUrl
|
||||
const bookingUrl = option.bookingUrl || option.url || option.links?.[0]?.url || ''
|
||||
return bookingUrl ? `/api/preview-image?url=${encodeURIComponent(bookingUrl)}` : ''
|
||||
}
|
||||
|
||||
function useCaboData() {
|
||||
const [state, setState] = useState({
|
||||
categories: [],
|
||||
@@ -367,7 +372,7 @@ function MainRoute({ data, tabs, guest, token, send, mobileView, setMobileView }
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={cx('grid gap-4', showMap && 'md:grid-cols-[minmax(0,1fr)_minmax(360px,42vw)]')}>
|
||||
<div className={cx('grid gap-4', showMap && 'lg:grid-cols-[minmax(420px,520px)_minmax(0,1fr)] xl:grid-cols-[minmax(520px,640px)_minmax(0,1fr)]')}>
|
||||
<section className={cx(showMap && mobileView === 'map' && 'hidden md:block')}>
|
||||
<OptionList tabId={tabId} tab={tab} options={visibleOptions} data={data} guest={guest} token={token} send={send} />
|
||||
<AddOptionForm categories={data.categories} token={token} guest={guest} />
|
||||
@@ -438,6 +443,7 @@ function OptionCard({ option, tabId, guest, token, send }) {
|
||||
const latestPoint = getLatestPoint(option, tabId)
|
||||
const source = getAvailableSources(option, tabId)[0]
|
||||
const bookingUrl = getBookingUrl(option, tabId)
|
||||
const imageUrl = getOptionImageUrl({ ...option, bookingUrl })
|
||||
const links = getVisibleLinks(option, tabId)
|
||||
const price = latestPoint?.displayPrice || (typeof latestPoint?.price === 'number' ? formatMoney(latestPoint.price, latestPoint.currency) : source?.latestDisplayPrice || '')
|
||||
const dates = [formatDate(latestPoint?.tripCheckIn), formatDate(latestPoint?.tripCheckOut)].filter(Boolean).join(' to ')
|
||||
@@ -453,21 +459,30 @@ function OptionCard({ option, tabId, guest, token, send }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<article className={cx('overflow-hidden rounded-xl border bg-panel shadow-lg transition hover:-translate-y-0.5 hover:border-aqua/70', hasVoted ? 'border-aqua/70' : 'border-line')}>
|
||||
<div className="grid gap-0 md:grid-cols-[minmax(132px,34%)_1fr]">
|
||||
<div className="min-h-44 bg-panel2">
|
||||
<img src={option.imageUrl} alt="" className="h-full min-h-44 w-full object-cover" loading="lazy" />
|
||||
<article className={cx('min-w-0 overflow-hidden rounded-xl border bg-panel shadow-lg transition hover:-translate-y-0.5 hover:border-aqua/70', hasVoted ? 'border-aqua/70' : 'border-line')}>
|
||||
<div className="grid gap-0">
|
||||
<div className="grid h-44 place-items-center overflow-hidden bg-gradient-to-br from-aqua/10 via-panel2 to-gold/10 sm:h-52">
|
||||
{imageUrl ? (
|
||||
<img
|
||||
src={imageUrl}
|
||||
alt={`${option.name} booking site preview`}
|
||||
className="h-full w-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<div className="px-6 text-center text-sm font-black uppercase tracking-wider text-slate-500">No booking image</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="min-w-0 p-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<h2 className="text-base font-black text-white">{option.name}</h2>
|
||||
<p className="mt-1 text-sm leading-5 text-slate-400">{option.desc}</p>
|
||||
<div className="min-w-0">
|
||||
<h2 className="break-words text-base font-black text-white">{option.name}</h2>
|
||||
<p className="mt-1 break-words text-sm leading-5 text-slate-400">{option.desc}</p>
|
||||
</div>
|
||||
<div className="shrink-0 text-sm font-black text-aqua">{votes.length} vote{votes.length === 1 ? '' : 's'}</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 grid gap-2 sm:grid-cols-2">
|
||||
<div className="mt-3 grid gap-2 min-[560px]:grid-cols-2">
|
||||
<Fact label="Current price" value={price || 'Not tracked yet'} sub={dates} />
|
||||
<Fact label="Source" value={source?.sourceLabel || latestPoint?.source || 'Planning data'} />
|
||||
<Fact label="Booking" value={formatBookingType(source?.bookingType || latestPoint?.bookingType, source?.priceBasis || latestPoint?.priceBasis) || 'Option link'} />
|
||||
@@ -476,7 +491,7 @@ function OptionCard({ option, tabId, guest, token, send }) {
|
||||
|
||||
{!!chips.length && (
|
||||
<div className="mt-3 flex flex-wrap gap-1.5">
|
||||
{chips.map((chip) => <span key={chip} className="rounded-full bg-panel2 px-2.5 py-1 text-[11px] text-slate-300">{chip}</span>)}
|
||||
{chips.map((chip) => <span key={chip} className="max-w-full rounded-full bg-panel2 px-2.5 py-1 text-[11px] text-slate-300">{chip}</span>)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -487,7 +502,7 @@ function OptionCard({ option, tabId, guest, token, send }) {
|
||||
</a>
|
||||
)}
|
||||
{links.slice(0, 4).map((link) => (
|
||||
<a key={`${option.id}-${link.label}`} href={link.url} target="_blank" rel="noreferrer" className="rounded-full border border-aqua/20 bg-aqua/10 px-3 py-2 text-xs font-bold text-aqua hover:bg-aqua/20">
|
||||
<a key={`${option.id}-${link.label}`} href={link.url} target="_blank" rel="noreferrer" className="max-w-full rounded-full border border-aqua/20 bg-aqua/10 px-3 py-2 text-xs font-bold text-aqua hover:bg-aqua/20">
|
||||
{link.label}
|
||||
</a>
|
||||
))}
|
||||
@@ -508,10 +523,10 @@ function OptionCard({ option, tabId, guest, token, send }) {
|
||||
|
||||
function Fact({ label, value, sub }) {
|
||||
return (
|
||||
<div className="rounded-lg border border-white/5 bg-white/[0.03] p-3">
|
||||
<div className="min-w-0 rounded-lg border border-white/5 bg-white/[0.03] p-3">
|
||||
<div className="text-[10px] font-black uppercase tracking-wider text-slate-500">{label}</div>
|
||||
<div className="mt-1 text-sm font-bold text-white">{value}</div>
|
||||
{sub && <div className="mt-1 text-xs text-slate-500">{sub}</div>}
|
||||
<div className="mt-1 break-words text-sm font-bold leading-5 text-white">{value}</div>
|
||||
{sub && <div className="mt-1 break-words text-xs leading-4 text-slate-500">{sub}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user