Compare commits

..

No commits in common. "1fe2da1f6651675272ce3bfc8fafb50cc607fa90" and "d9a570016250b43ff222d0653fc14f4e96a3143f" have entirely different histories.

4 changed files with 44 additions and 81 deletions

View file

@ -221,7 +221,7 @@ export async function POST(req: Request) {
const calendarUrls = normalizeCalendarUrls(body.calendarUrls); const calendarUrls = normalizeCalendarUrls(body.calendarUrls);
const translationsInputRaw = Array.isArray(body.translations) ? body.translations : []; const translationsInputRaw = Array.isArray(body.translations) ? body.translations : [];
type TranslationInput = { locale: string; title: string; description: string; teaser: string | null; slug: string }; type TranslationInput = { locale: string; title: string; description: string; teaser: string | null; slug: string };
let translationsInput = const translationsInput =
translationsInputRaw translationsInputRaw
.map((item: any) => ({ .map((item: any) => ({
locale: String(item.locale ?? '').toLowerCase(), locale: String(item.locale ?? '').toLowerCase(),
@ -230,18 +230,18 @@ export async function POST(req: Request) {
teaser: typeof item.teaser === 'string' ? item.teaser.trim() : null, teaser: typeof item.teaser === 'string' ? item.teaser.trim() : null,
slug: String(item.slug ?? slug).trim().toLowerCase(), slug: String(item.slug ?? slug).trim().toLowerCase(),
})) }))
.filter((t: any) => t.locale && (saveDraft || (t.title && t.description))) || []; .filter((t: any) => t.locale && t.title && t.description) || [];
const fallbackLocale = String(body.locale ?? 'en').toLowerCase(); const fallbackLocale = String(body.locale ?? 'en').toLowerCase();
const fallbackTranslationTitle = typeof body.title === 'string' ? body.title.trim() : ''; const fallbackTranslationTitle = typeof body.title === 'string' ? body.title.trim() : '';
const fallbackTranslationDescription = typeof body.description === 'string' ? body.description.trim() : ''; const fallbackTranslationDescription = typeof body.description === 'string' ? body.description.trim() : '';
const fallbackTranslationTeaser = typeof body.teaser === 'string' ? body.teaser.trim() : null; const fallbackTranslationTeaser = typeof body.teaser === 'string' ? body.teaser.trim() : null;
if (translationsInput.length === 0 && (fallbackTranslationTitle || saveDraft) && (fallbackTranslationDescription || saveDraft)) { if (translationsInput.length === 0 && fallbackTranslationTitle && fallbackTranslationDescription) {
translationsInput.push({ translationsInput.push({
locale: fallbackLocale, locale: fallbackLocale,
title: fallbackTranslationTitle ?? '', title: fallbackTranslationTitle,
description: fallbackTranslationDescription ?? '', description: fallbackTranslationDescription,
teaser: fallbackTranslationTeaser, teaser: fallbackTranslationTeaser,
slug, slug,
}); });

View file

@ -1,5 +1,4 @@
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { ListingStatus } from '@prisma/client';
import Link from 'next/link'; import Link from 'next/link';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { cookies, headers } from 'next/headers'; import { cookies, headers } from 'next/headers';
@ -7,7 +6,6 @@ import { getListingBySlug, DEFAULT_LOCALE, withResolvedListingImages } from '../
import { SAMPLE_LISTING_SLUGS } from '../../../lib/sampleListing'; import { SAMPLE_LISTING_SLUGS } from '../../../lib/sampleListing';
import { resolveLocale, t as translate } from '../../../lib/i18n'; import { resolveLocale, t as translate } from '../../../lib/i18n';
import { getCalendarRanges, expandBlockedDates } from '../../../lib/calendar'; import { getCalendarRanges, expandBlockedDates } from '../../../lib/calendar';
import { verifyAccessToken } from '../../../lib/jwt';
import AvailabilityCalendar from '../../components/AvailabilityCalendar'; import AvailabilityCalendar from '../../components/AvailabilityCalendar';
type ListingPageProps = { type ListingPageProps = {
@ -44,22 +42,8 @@ export default async function ListingPage({ params }: ListingPageProps) {
const cookieStore = cookies(); const cookieStore = cookies();
const locale = resolveLocale({ cookieLocale: cookieStore.get('locale')?.value, acceptLanguage: headers().get('accept-language') }); const locale = resolveLocale({ cookieLocale: cookieStore.get('locale')?.value, acceptLanguage: headers().get('accept-language') });
const t = (key: any, vars?: Record<string, string | number>) => translate(locale, key as any, vars); const t = (key: any, vars?: Record<string, string | number>) => translate(locale, key as any, vars);
const sessionToken = cookieStore.get('session_token')?.value;
let viewerId: string | null = null;
if (sessionToken) {
try {
const payload = await verifyAccessToken(sessionToken);
viewerId = payload.userId;
} catch {
viewerId = null;
}
}
const translationRaw = await getListingBySlug({ const translationRaw = await getListingBySlug({ slug: params.slug, locale: locale ?? DEFAULT_LOCALE });
slug: params.slug,
locale: locale ?? DEFAULT_LOCALE,
includeOwnerDraftsForUserId: viewerId ?? undefined,
});
const translation = translationRaw ? withResolvedListingImages(translationRaw) : null; const translation = translationRaw ? withResolvedListingImages(translationRaw) : null;
if (!translation) { if (!translation) {
@ -116,8 +100,6 @@ export default async function ListingPage({ params }: ListingPageProps) {
.filter(Boolean) .filter(Boolean)
.join(' · ') .join(' · ')
: t('priceNotSet'); : t('priceNotSet');
const isDraftOrPending = listing.status !== ListingStatus.PUBLISHED;
const isOwnerView = viewerId && listing.ownerId === viewerId;
return ( return (
<main className="listing-shell"> <main className="listing-shell">
@ -126,11 +108,6 @@ export default async function ListingPage({ params }: ListingPageProps) {
</div> </div>
<div className="listing-layout"> <div className="listing-layout">
<div className="panel listing-main"> <div className="panel listing-main">
{isDraftOrPending ? (
<div className="badge warning" style={{ marginBottom: 10, display: 'inline-block' }}>
{isOwnerView ? t('statusLabel') : 'Status'}: {listing.status}
</div>
) : null}
{isSample ? ( {isSample ? (
<div className="badge warning" style={{ marginBottom: 10, display: 'inline-block' }}> <div className="badge warning" style={{ marginBottom: 10, display: 'inline-block' }}>
{t('sampleBadge')} {t('sampleBadge')}

View file

@ -387,43 +387,41 @@ export default function NewListingPage() {
? t('createListingSuccess', { id: data.listing.id, status: 'DRAFT' }) ? t('createListingSuccess', { id: data.listing.id, status: 'DRAFT' })
: t('createListingSuccess', { id: data.listing.id, status: data.listing.status }), : t('createListingSuccess', { id: data.listing.id, status: data.listing.status }),
); );
if (!saveDraft) { setSlug('');
setSlug(''); setTranslations({
setTranslations({ en: { title: '', description: '', teaser: '' },
en: { title: '', description: '', teaser: '' }, fi: { title: '', description: '', teaser: '' },
fi: { title: '', description: '', teaser: '' }, sv: { title: '', description: '', teaser: '' },
sv: { title: '', description: '', teaser: '' }, });
}); setMaxGuests(4);
setMaxGuests(4); setBedrooms(2);
setBedrooms(2); setBeds(3);
setBeds(3); setBathrooms(1);
setBathrooms(1); setPriceWeekday('');
setPriceWeekday(''); setPriceWeekend('');
setPriceWeekend(''); setHasSauna(true);
setHasSauna(true); setHasFireplace(true);
setHasFireplace(true); setHasWifi(true);
setHasWifi(true); setPetsAllowed(false);
setPetsAllowed(false); setByTheLake(false);
setByTheLake(false); setHasAirConditioning(false);
setHasAirConditioning(false); setHasKitchen(true);
setHasKitchen(true); setHasDishwasher(false);
setHasDishwasher(false); setHasWashingMachine(false);
setHasWashingMachine(false); setHasBarbecue(false);
setHasBarbecue(false); setHasMicrowave(false);
setHasMicrowave(false); setHasFreeParking(false);
setHasFreeParking(false); setRegion('');
setRegion(''); setCity('');
setCity(''); setStreetAddress('');
setStreetAddress(''); setAddressNote('');
setAddressNote(''); setLatitude('');
setLatitude(''); setLongitude('');
setLongitude(''); setContactName('');
setContactName(''); setContactEmail('');
setContactEmail(''); setCalendarUrls('');
setCalendarUrls(''); setSelectedImages([]);
setSelectedImages([]); setCoverImageIndex(1);
setCoverImageIndex(1);
}
} }
} catch (err) { } catch (err) {
setError('Failed to create listing'); setError('Failed to create listing');

View file

@ -16,7 +16,6 @@ export type ListingWithTranslations = Prisma.ListingTranslationGetPayload<{
type FetchOptions = { type FetchOptions = {
slug: string; slug: string;
locale?: string; locale?: string;
includeOwnerDraftsForUserId?: string;
}; };
function resolveImageUrl(img: { id: string; url: string | null; size: number | null }) { function resolveImageUrl(img: { id: string; url: string | null; size: number | null }) {
@ -30,22 +29,11 @@ function resolveImageUrl(img: { id: string; url: string | null; size: number | n
* Fetch a listing translation by slug and locale. * Fetch a listing translation by slug and locale.
* Falls back to any locale if the requested locale is missing. * Falls back to any locale if the requested locale is missing.
*/ */
export async function getListingBySlug({ slug, locale, includeOwnerDraftsForUserId }: FetchOptions): Promise<ListingWithTranslations | null> { export async function getListingBySlug({ slug, locale }: FetchOptions): Promise<ListingWithTranslations | null> {
const targetLocale = locale ?? DEFAULT_LOCALE; const targetLocale = locale ?? DEFAULT_LOCALE;
const listingWhere: Prisma.ListingWhereInput =
includeOwnerDraftsForUserId
? {
removedAt: null,
OR: [
{ status: ListingStatus.PUBLISHED },
{ ownerId: includeOwnerDraftsForUserId, status: { in: [ListingStatus.DRAFT, ListingStatus.PENDING] } },
],
}
: { status: ListingStatus.PUBLISHED, removedAt: null };
const translation = await prisma.listingTranslation.findFirst({ const translation = await prisma.listingTranslation.findFirst({
where: { slug, locale: targetLocale, listing: listingWhere }, where: { slug, locale: targetLocale, listing: { status: ListingStatus.PUBLISHED, removedAt: null } },
include: { include: {
listing: { listing: {
include: { include: {
@ -62,7 +50,7 @@ export async function getListingBySlug({ slug, locale, includeOwnerDraftsForUser
// Fallback: first translation for this slug // Fallback: first translation for this slug
return prisma.listingTranslation.findFirst({ return prisma.listingTranslation.findFirst({
where: { slug, listing: listingWhere }, where: { slug, listing: { status: ListingStatus.PUBLISHED, removedAt: null } },
include: { include: {
listing: { listing: {
include: { include: {