Compare commits
2 commits
835f47779e
...
b03743dde6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b03743dde6 | ||
|
|
928c2f9bb9 |
5 changed files with 43 additions and 17 deletions
|
|
@ -88,3 +88,4 @@
|
|||
- Forgejo deployment scaffolding added: Docker Compose + runner config guidance and Apache vhost for git.halla-aho.net, plus CI workflow placeholder under `.forgejo/workflows/`.
|
||||
- Amenities: added separate EV charging flags (on-site vs nearby) plus wheelchair accessibility, including browse filters and admin approvals view badges.
|
||||
- Navbar: combined admin actions (approvals/users/monitoring) under a single “Admin” dropdown menu.
|
||||
- Pricing copy: treat listing prices as indicative “starting from” values and show starting-from line on browse cards + home latest carousel.
|
||||
|
|
|
|||
|
|
@ -114,11 +114,17 @@ export default async function ListingPage({ params }: ListingPageProps) {
|
|||
const capacityLine = capacityParts.length ? capacityParts.join(' · ') : t('capacityUnknown');
|
||||
const contactLine = `${listing.contactName} · ${listing.contactEmail}${listing.contactPhone ? ` · ${listing.contactPhone}` : ''}`;
|
||||
const coverImage = listing.images.find((img) => img.isCover) ?? listing.images[0] ?? null;
|
||||
const priceCandidates = [listing.priceWeekdayEuros, listing.priceWeekendEuros].filter((p): p is number => typeof p === 'number');
|
||||
const startingFromEuros = priceCandidates.length ? Math.min(...priceCandidates) : null;
|
||||
const priceLine =
|
||||
listing.priceWeekdayEuros || listing.priceWeekendEuros
|
||||
? [listing.priceWeekdayEuros ? t('priceWeekdayShort', { price: listing.priceWeekdayEuros }) : null, listing.priceWeekendEuros ? t('priceWeekendShort', { price: listing.priceWeekendEuros }) : null]
|
||||
.filter(Boolean)
|
||||
.join(' · ')
|
||||
? `${startingFromEuros !== null ? t('priceStartingFromShort', { price: startingFromEuros }) : ''}${
|
||||
listing.priceWeekdayEuros || listing.priceWeekendEuros
|
||||
? ` (${[listing.priceWeekdayEuros ? t('priceWeekdayShort', { price: listing.priceWeekdayEuros }) : null, listing.priceWeekendEuros ? t('priceWeekendShort', { price: listing.priceWeekendEuros }) : null]
|
||||
.filter(Boolean)
|
||||
.join(' · ')})`
|
||||
: ''
|
||||
}`
|
||||
: t('priceNotSet');
|
||||
const isDraftOrPending = listing.status !== ListingStatus.PUBLISHED;
|
||||
const isOwnerView = viewerId && listing.ownerId === viewerId;
|
||||
|
|
|
|||
|
|
@ -469,13 +469,18 @@ export default function ListingsIndexPage() {
|
|||
</span>
|
||||
) : null}
|
||||
<p style={{ margin: 0 }}>{l.teaser ?? ''}</p>
|
||||
{l.priceWeekdayEuros || l.priceWeekendEuros ? (
|
||||
<div style={{ color: '#cbd5e1', fontSize: 14 }}>
|
||||
{t('priceStartingFromShort', {
|
||||
price: Math.min(...([l.priceWeekdayEuros, l.priceWeekendEuros].filter((p): p is number => typeof p === 'number'))),
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
<div style={{ color: '#cbd5e1', fontSize: 14 }}>
|
||||
{l.streetAddress ? `${l.streetAddress}, ` : ''}
|
||||
{l.city}, {l.region}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', fontSize: 13 }}>
|
||||
{l.priceWeekdayEuros ? <span className="badge">{t('priceWeekdayShort', { price: l.priceWeekdayEuros })}</span> : null}
|
||||
{l.priceWeekendEuros ? <span className="badge">{t('priceWeekendShort', { price: l.priceWeekendEuros })}</span> : null}
|
||||
<span className="badge">{t('capacityGuests', { count: l.maxGuests })}</span>
|
||||
<span className="badge">{t('capacityBedrooms', { count: l.bedrooms })}</span>
|
||||
{l.hasCalendar ? <span className="badge secondary">{t('calendarConnected')}</span> : null}
|
||||
|
|
|
|||
11
app/page.tsx
11
app/page.tsx
|
|
@ -13,6 +13,8 @@ type LatestListing = {
|
|||
city: string;
|
||||
region: string;
|
||||
isSample: boolean;
|
||||
priceWeekdayEuros: number | null;
|
||||
priceWeekendEuros: number | null;
|
||||
};
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
|
@ -98,6 +100,15 @@ export default function HomePage() {
|
|||
</div>
|
||||
<h3 style={{ margin: '6px 0 4px' }}>{item.title}</h3>
|
||||
<p style={{ margin: 0 }}>{item.teaser}</p>
|
||||
{item.priceWeekdayEuros || item.priceWeekendEuros ? (
|
||||
<div style={{ color: '#cbd5e1', fontSize: 14, marginTop: 2 }}>
|
||||
{t('priceStartingFromShort', {
|
||||
price: Math.min(
|
||||
...([item.priceWeekdayEuros, item.priceWeekendEuros].filter((p): p is number => typeof p === 'number')),
|
||||
),
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
<div style={{ display: 'flex', gap: 8, marginTop: 10, flexWrap: 'wrap' }}>
|
||||
<Link className="button secondary" href={`/listings/${item.slug}`}>
|
||||
{t('openListing')}
|
||||
|
|
|
|||
27
lib/i18n.ts
27
lib/i18n.ts
|
|
@ -153,7 +153,7 @@ const baseMessages = {
|
|||
listingLocation: 'Location',
|
||||
listingAddress: 'Address',
|
||||
listingCapacity: 'Capacity',
|
||||
listingPrices: 'Pricing',
|
||||
listingPrices: 'Price (starting from)',
|
||||
listingAmenities: 'Amenities',
|
||||
listingNoAmenities: 'No amenities listed yet.',
|
||||
listingContact: 'Contact',
|
||||
|
|
@ -218,9 +218,10 @@ const baseMessages = {
|
|||
bedroomsLabel: 'Bedrooms',
|
||||
bedsLabel: 'Beds',
|
||||
bathroomsLabel: 'Bathrooms',
|
||||
priceWeekdayLabel: 'Weeknight price (€ / night)',
|
||||
priceWeekendLabel: 'Weekend price (€ / night)',
|
||||
priceHintHelp: 'Set separate weeknight and weekend prices in euros (optional, not a binding offer).',
|
||||
priceWeekdayLabel: 'Price starting from (weeknight, € / night)',
|
||||
priceWeekendLabel: 'Price starting from (weekend, € / night)',
|
||||
priceHintHelp: 'These prices are indicative only (starting from), not a binding offer.',
|
||||
priceStartingFromShort: 'Starting from {price}€ / night',
|
||||
priceWeekdayShort: '{price}€ weekday',
|
||||
priceWeekendShort: '{price}€ weekend',
|
||||
priceNotSet: 'Not provided',
|
||||
|
|
@ -500,7 +501,7 @@ const baseMessages = {
|
|||
listingLocation: 'Sijainti',
|
||||
listingAddress: 'Osoite',
|
||||
listingCapacity: 'Tilat',
|
||||
listingPrices: 'Hinta',
|
||||
listingPrices: 'Hinta (alkaen)',
|
||||
listingAmenities: 'Varustelu',
|
||||
listingNoAmenities: 'Varustelua ei ole listattu.',
|
||||
listingContact: 'Yhteystiedot',
|
||||
|
|
@ -538,9 +539,10 @@ const baseMessages = {
|
|||
bedroomsLabel: 'Makuuhuoneita',
|
||||
bedsLabel: 'Vuoteita',
|
||||
bathroomsLabel: 'Kylpyhuoneita',
|
||||
priceWeekdayLabel: 'Arkiyön hinta (€ / yö)',
|
||||
priceWeekendLabel: 'Viikonlopun hinta (€ / yö)',
|
||||
priceHintHelp: 'Aseta erilliset hinnat arki- ja viikonloppuyöille euroissa (valinnainen, ei sitova).',
|
||||
priceWeekdayLabel: 'Hinta alkaen (arki, € / yö)',
|
||||
priceWeekendLabel: 'Hinta alkaen (viikonloppu, € / yö)',
|
||||
priceHintHelp: 'Hinnat ovat suuntaa-antavia (alkaen), eivät sitovia.',
|
||||
priceStartingFromShort: 'Alkaen {price}€ / yö',
|
||||
priceWeekdayShort: '{price}€ arki',
|
||||
priceWeekendShort: '{price}€ viikonloppu',
|
||||
priceNotSet: 'Ei ilmoitettu',
|
||||
|
|
@ -719,13 +721,14 @@ const svMessages: Record<keyof typeof baseMessages.en, string> = {
|
|||
slugTaken: 'Sluggen används redan',
|
||||
slugCheckError: 'Kunde inte kontrollera sluggen nu',
|
||||
teaserHelp: 'Kort ingress som syns i korten',
|
||||
priceWeekdayLabel: 'Vardagspris (€ / natt)',
|
||||
priceWeekendLabel: 'Helgpris (€ / natt)',
|
||||
priceHintHelp: 'Ange separata priser för vardag och helg i euro per natt (frivilligt).',
|
||||
priceWeekdayLabel: 'Pris från (vardag, € / natt)',
|
||||
priceWeekendLabel: 'Pris från (helg, € / natt)',
|
||||
priceHintHelp: 'Priserna är endast vägledande (från), inte ett bindande erbjudande.',
|
||||
priceStartingFromShort: 'Från {price}€ / natt',
|
||||
priceWeekdayShort: '{price}€ vardag',
|
||||
priceWeekendShort: '{price}€ helg',
|
||||
priceNotSet: 'Ej angivet',
|
||||
listingPrices: 'Priser',
|
||||
listingPrices: 'Pris (från)',
|
||||
capacityUnknown: 'Kapacitet ej angiven',
|
||||
amenityEvAvailable: 'EV-laddning i närheten',
|
||||
amenityEvNearby: 'EV-laddning i närheten',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue