Fix draft listing save/reset and allow viewing drafts
This commit is contained in:
parent
d9a5700162
commit
0b5ca0a190
4 changed files with 81 additions and 44 deletions
|
|
@ -221,7 +221,7 @@ export async function POST(req: Request) {
|
|||
const calendarUrls = normalizeCalendarUrls(body.calendarUrls);
|
||||
const translationsInputRaw = Array.isArray(body.translations) ? body.translations : [];
|
||||
type TranslationInput = { locale: string; title: string; description: string; teaser: string | null; slug: string };
|
||||
const translationsInput =
|
||||
let translationsInput =
|
||||
translationsInputRaw
|
||||
.map((item: any) => ({
|
||||
locale: String(item.locale ?? '').toLowerCase(),
|
||||
|
|
@ -230,18 +230,18 @@ export async function POST(req: Request) {
|
|||
teaser: typeof item.teaser === 'string' ? item.teaser.trim() : null,
|
||||
slug: String(item.slug ?? slug).trim().toLowerCase(),
|
||||
}))
|
||||
.filter((t: any) => t.locale && t.title && t.description) || [];
|
||||
.filter((t: any) => t.locale && (saveDraft || (t.title && t.description))) || [];
|
||||
|
||||
const fallbackLocale = String(body.locale ?? 'en').toLowerCase();
|
||||
const fallbackTranslationTitle = typeof body.title === 'string' ? body.title.trim() : '';
|
||||
const fallbackTranslationDescription = typeof body.description === 'string' ? body.description.trim() : '';
|
||||
const fallbackTranslationTeaser = typeof body.teaser === 'string' ? body.teaser.trim() : null;
|
||||
|
||||
if (translationsInput.length === 0 && fallbackTranslationTitle && fallbackTranslationDescription) {
|
||||
if (translationsInput.length === 0 && (fallbackTranslationTitle || saveDraft) && (fallbackTranslationDescription || saveDraft)) {
|
||||
translationsInput.push({
|
||||
locale: fallbackLocale,
|
||||
title: fallbackTranslationTitle,
|
||||
description: fallbackTranslationDescription,
|
||||
title: fallbackTranslationTitle ?? '',
|
||||
description: fallbackTranslationDescription ?? '',
|
||||
teaser: fallbackTranslationTeaser,
|
||||
slug,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { Metadata } from 'next';
|
||||
import { ListingStatus } from '@prisma/client';
|
||||
import Link from 'next/link';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { cookies, headers } from 'next/headers';
|
||||
|
|
@ -6,6 +7,7 @@ import { getListingBySlug, DEFAULT_LOCALE, withResolvedListingImages } from '../
|
|||
import { SAMPLE_LISTING_SLUGS } from '../../../lib/sampleListing';
|
||||
import { resolveLocale, t as translate } from '../../../lib/i18n';
|
||||
import { getCalendarRanges, expandBlockedDates } from '../../../lib/calendar';
|
||||
import { verifyAccessToken } from '../../../lib/jwt';
|
||||
import AvailabilityCalendar from '../../components/AvailabilityCalendar';
|
||||
|
||||
type ListingPageProps = {
|
||||
|
|
@ -42,8 +44,22 @@ export default async function ListingPage({ params }: ListingPageProps) {
|
|||
const cookieStore = cookies();
|
||||
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 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({ slug: params.slug, locale: locale ?? DEFAULT_LOCALE });
|
||||
const translationRaw = await getListingBySlug({
|
||||
slug: params.slug,
|
||||
locale: locale ?? DEFAULT_LOCALE,
|
||||
includeOwnerDraftsForUserId: viewerId ?? undefined,
|
||||
});
|
||||
const translation = translationRaw ? withResolvedListingImages(translationRaw) : null;
|
||||
|
||||
if (!translation) {
|
||||
|
|
@ -100,6 +116,8 @@ export default async function ListingPage({ params }: ListingPageProps) {
|
|||
.filter(Boolean)
|
||||
.join(' · ')
|
||||
: t('priceNotSet');
|
||||
const isDraftOrPending = listing.status !== ListingStatus.PUBLISHED;
|
||||
const isOwnerView = viewerId && listing.ownerId === viewerId;
|
||||
|
||||
return (
|
||||
<main className="listing-shell">
|
||||
|
|
@ -108,6 +126,11 @@ export default async function ListingPage({ params }: ListingPageProps) {
|
|||
</div>
|
||||
<div className="listing-layout">
|
||||
<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 ? (
|
||||
<div className="badge warning" style={{ marginBottom: 10, display: 'inline-block' }}>
|
||||
{t('sampleBadge')}
|
||||
|
|
|
|||
|
|
@ -387,41 +387,43 @@ export default function NewListingPage() {
|
|||
? t('createListingSuccess', { id: data.listing.id, status: 'DRAFT' })
|
||||
: t('createListingSuccess', { id: data.listing.id, status: data.listing.status }),
|
||||
);
|
||||
setSlug('');
|
||||
setTranslations({
|
||||
en: { title: '', description: '', teaser: '' },
|
||||
fi: { title: '', description: '', teaser: '' },
|
||||
sv: { title: '', description: '', teaser: '' },
|
||||
});
|
||||
setMaxGuests(4);
|
||||
setBedrooms(2);
|
||||
setBeds(3);
|
||||
setBathrooms(1);
|
||||
setPriceWeekday('');
|
||||
setPriceWeekend('');
|
||||
setHasSauna(true);
|
||||
setHasFireplace(true);
|
||||
setHasWifi(true);
|
||||
setPetsAllowed(false);
|
||||
setByTheLake(false);
|
||||
setHasAirConditioning(false);
|
||||
setHasKitchen(true);
|
||||
setHasDishwasher(false);
|
||||
setHasWashingMachine(false);
|
||||
setHasBarbecue(false);
|
||||
setHasMicrowave(false);
|
||||
setHasFreeParking(false);
|
||||
setRegion('');
|
||||
setCity('');
|
||||
setStreetAddress('');
|
||||
setAddressNote('');
|
||||
setLatitude('');
|
||||
setLongitude('');
|
||||
setContactName('');
|
||||
setContactEmail('');
|
||||
setCalendarUrls('');
|
||||
setSelectedImages([]);
|
||||
setCoverImageIndex(1);
|
||||
if (!saveDraft) {
|
||||
setSlug('');
|
||||
setTranslations({
|
||||
en: { title: '', description: '', teaser: '' },
|
||||
fi: { title: '', description: '', teaser: '' },
|
||||
sv: { title: '', description: '', teaser: '' },
|
||||
});
|
||||
setMaxGuests(4);
|
||||
setBedrooms(2);
|
||||
setBeds(3);
|
||||
setBathrooms(1);
|
||||
setPriceWeekday('');
|
||||
setPriceWeekend('');
|
||||
setHasSauna(true);
|
||||
setHasFireplace(true);
|
||||
setHasWifi(true);
|
||||
setPetsAllowed(false);
|
||||
setByTheLake(false);
|
||||
setHasAirConditioning(false);
|
||||
setHasKitchen(true);
|
||||
setHasDishwasher(false);
|
||||
setHasWashingMachine(false);
|
||||
setHasBarbecue(false);
|
||||
setHasMicrowave(false);
|
||||
setHasFreeParking(false);
|
||||
setRegion('');
|
||||
setCity('');
|
||||
setStreetAddress('');
|
||||
setAddressNote('');
|
||||
setLatitude('');
|
||||
setLongitude('');
|
||||
setContactName('');
|
||||
setContactEmail('');
|
||||
setCalendarUrls('');
|
||||
setSelectedImages([]);
|
||||
setCoverImageIndex(1);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Failed to create listing');
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export type ListingWithTranslations = Prisma.ListingTranslationGetPayload<{
|
|||
type FetchOptions = {
|
||||
slug: string;
|
||||
locale?: string;
|
||||
includeOwnerDraftsForUserId?: string;
|
||||
};
|
||||
|
||||
function resolveImageUrl(img: { id: string; url: string | null; size: number | null }) {
|
||||
|
|
@ -29,11 +30,22 @@ function resolveImageUrl(img: { id: string; url: string | null; size: number | n
|
|||
* Fetch a listing translation by slug and locale.
|
||||
* Falls back to any locale if the requested locale is missing.
|
||||
*/
|
||||
export async function getListingBySlug({ slug, locale }: FetchOptions): Promise<ListingWithTranslations | null> {
|
||||
export async function getListingBySlug({ slug, locale, includeOwnerDraftsForUserId }: FetchOptions): Promise<ListingWithTranslations | null> {
|
||||
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({
|
||||
where: { slug, locale: targetLocale, listing: { status: ListingStatus.PUBLISHED, removedAt: null } },
|
||||
where: { slug, locale: targetLocale, listing: listingWhere },
|
||||
include: {
|
||||
listing: {
|
||||
include: {
|
||||
|
|
@ -50,7 +62,7 @@ export async function getListingBySlug({ slug, locale }: FetchOptions): Promise<
|
|||
|
||||
// Fallback: first translation for this slug
|
||||
return prisma.listingTranslation.findFirst({
|
||||
where: { slug, listing: { status: ListingStatus.PUBLISHED, removedAt: null } },
|
||||
where: { slug, listing: listingWhere },
|
||||
include: {
|
||||
listing: {
|
||||
include: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue