153 lines
4.6 KiB
TypeScript
153 lines
4.6 KiB
TypeScript
import { cache } from "react";
|
|
|
|
import { prisma } from "@/lib/prisma";
|
|
import { amenityLabel } from "@/lib/amenities";
|
|
import { AccessType, CarbetStatus, MediaType, TransportMode } from "@/generated/prisma/enums";
|
|
import type { CarbetReview, CarbetReviewStats } from "@/lib/reviews";
|
|
import {
|
|
getCarbetReviewStats,
|
|
listCarbetReviews,
|
|
} from "@/lib/reviews-server";
|
|
import type { PirogueProvider } from "@/lib/pirogue-providers";
|
|
|
|
export type PublicCarbetMedia = {
|
|
id: string;
|
|
type: MediaType;
|
|
url: string;
|
|
};
|
|
|
|
export type PublicCarbetDetail = {
|
|
id: string;
|
|
slug: string;
|
|
title: string;
|
|
description: string;
|
|
river: string;
|
|
embarkPoint: string;
|
|
pirogueDurationMin: number | null;
|
|
accessType: AccessType;
|
|
roadAccessNote: string | null;
|
|
capacity: number;
|
|
nightlyPrice: string;
|
|
minStayNights: number | null;
|
|
maxStayNights: number | null;
|
|
minCapacity: number | null;
|
|
seasonalConstraints: unknown;
|
|
transportMode: TransportMode | null;
|
|
pirogueProvider: PirogueProvider | null;
|
|
latitude: string;
|
|
longitude: string;
|
|
ownerId: string;
|
|
ownerFirstName: string;
|
|
media: PublicCarbetMedia[];
|
|
amenities: { key: string; label: string }[];
|
|
reviewStats: CarbetReviewStats;
|
|
reviews: CarbetReview[];
|
|
};
|
|
|
|
// Memoized within a single request so generateMetadata() and the page itself
|
|
// only hit the database once per render.
|
|
export const getPublicCarbet = cache(
|
|
async (slug: string): Promise<PublicCarbetDetail | null> => {
|
|
const carbet = await prisma.carbet.findFirst({
|
|
where: { slug, status: CarbetStatus.PUBLISHED },
|
|
select: {
|
|
id: true,
|
|
slug: true,
|
|
title: true,
|
|
description: true,
|
|
river: true,
|
|
embarkPoint: true,
|
|
pirogueDurationMin: true,
|
|
accessType: true,
|
|
roadAccessNote: true,
|
|
capacity: true,
|
|
nightlyPrice: true,
|
|
minStayNights: true,
|
|
maxStayNights: true,
|
|
minCapacity: true,
|
|
seasonalConstraints: true,
|
|
transportMode: true,
|
|
pirogueProviderId: true,
|
|
latitude: true,
|
|
longitude: true,
|
|
ownerId: true,
|
|
owner: { select: { firstName: true } },
|
|
pirogueProvider: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
contactEmail: true,
|
|
contactPhone: true,
|
|
rivers: true,
|
|
pricingNote: true,
|
|
description: true,
|
|
active: true,
|
|
},
|
|
},
|
|
media: {
|
|
orderBy: { sortOrder: "asc" },
|
|
select: { id: true, type: true, s3Url: true },
|
|
},
|
|
amenities: {
|
|
select: { amenity: { select: { key: true, label: true } } },
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!carbet) return null;
|
|
|
|
const [reviewStats, reviews] = await Promise.all([
|
|
getCarbetReviewStats(carbet.id),
|
|
listCarbetReviews(carbet.id, 20),
|
|
]);
|
|
|
|
return {
|
|
id: carbet.id,
|
|
slug: carbet.slug,
|
|
title: carbet.title,
|
|
description: carbet.description,
|
|
river: carbet.river,
|
|
embarkPoint: carbet.embarkPoint,
|
|
pirogueDurationMin: carbet.pirogueDurationMin,
|
|
accessType: carbet.accessType,
|
|
roadAccessNote: carbet.roadAccessNote,
|
|
capacity: carbet.capacity,
|
|
nightlyPrice: carbet.nightlyPrice.toString(),
|
|
minStayNights: carbet.minStayNights,
|
|
maxStayNights: carbet.maxStayNights,
|
|
minCapacity: carbet.minCapacity,
|
|
seasonalConstraints: carbet.seasonalConstraints,
|
|
transportMode: carbet.transportMode,
|
|
pirogueProvider: carbet.pirogueProvider
|
|
? {
|
|
id: carbet.pirogueProvider.id,
|
|
name: carbet.pirogueProvider.name,
|
|
contactEmail: carbet.pirogueProvider.contactEmail,
|
|
contactPhone: carbet.pirogueProvider.contactPhone,
|
|
rivers: carbet.pirogueProvider.rivers,
|
|
pricingNote: carbet.pirogueProvider.pricingNote,
|
|
description: carbet.pirogueProvider.description,
|
|
active: carbet.pirogueProvider.active,
|
|
}
|
|
: null,
|
|
latitude: carbet.latitude.toString(),
|
|
longitude: carbet.longitude.toString(),
|
|
ownerId: carbet.ownerId,
|
|
ownerFirstName: carbet.owner.firstName,
|
|
media: carbet.media.map((m) => ({
|
|
id: m.id,
|
|
type: m.type,
|
|
url: m.s3Url,
|
|
})),
|
|
amenities: carbet.amenities
|
|
.map((entry) => ({
|
|
key: entry.amenity.key,
|
|
// Prefer the catalogue label so renames roll out without a backfill.
|
|
label: amenityLabel(entry.amenity.key) || entry.amenity.label,
|
|
}))
|
|
.sort((a, b) => a.label.localeCompare(b.label, "fr")),
|
|
reviewStats,
|
|
reviews,
|
|
};
|
|
},
|
|
);
|