add user profile, profile vibe and trip vibe and social signal

This commit is contained in:
2026-05-08 19:20:27 +07:00
parent 3228ef712f
commit 7f419638b5
39 changed files with 1361 additions and 192 deletions
+41 -9
View File
@@ -1,11 +1,25 @@
import type { Metadata } from "next";
import Link from "next/link";
import Image from "next/image";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { tripService } from "@/server/services/trip.service";
import { profileRepo } from "@/server/repositories/profile.repo";
import { TripCard } from "@/features/trip/components/trip-card";
import { siteConfig, siteUrl, absoluteUrl } from "@/lib/site";
import { ACTIVITY_CATEGORIES, categoryMeta } from "@/lib/activity-category";
type OpenTrip = Awaited<ReturnType<typeof tripService.getOpenTrips>>[number];
function mapParticipants(trip: OpenTrip) {
return trip.participants.map((p) => ({
id: p.id,
name: p.user.name,
image: p.user.image,
interests: p.user.profile?.interests ?? [],
}));
}
export const metadata: Metadata = {
title: "Cari Teman Trip & Aktivitas — Pergi Bareng, Bukan Sendiri",
description: `${siteConfig.slogan} ${siteConfig.description}`,
@@ -18,7 +32,14 @@ export const metadata: Metadata = {
};
export default async function HomePage() {
const trips = await tripService.getOpenTrips();
const session = await getServerSession(authOptions);
const [trips, viewerProfile] = await Promise.all([
tripService.getOpenTrips(),
session?.user?.id
? profileRepo.findByUserId(session.user.id)
: Promise.resolve(null),
]);
const viewerInterests = viewerProfile?.interests ?? [];
const now = new Date();
const nextWeek = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
@@ -35,8 +56,10 @@ export default async function HomePage() {
const shownIds = new Set([...upcomingIds, ...latestTrips.map((t) => t.id)]);
const budgetTrips = trips
.filter((t) => !shownIds.has(t.id) && t.price <= 300000)
// Section sosial: trip yang paling ramai joiner-nya (social proof, bukan price proof).
const buzzingTrips = trips
.filter((t) => !shownIds.has(t.id) && t._count.participants > 0)
.sort((a, b) => b._count.participants - a._count.participants)
.slice(0, 3);
const orgJsonLd = {
@@ -191,6 +214,7 @@ export default async function HomePage() {
id={trip.id}
title={trip.title}
category={trip.category}
vibe={trip.vibe}
destination={trip.destination}
location={trip.location}
date={trip.date}
@@ -204,6 +228,8 @@ export default async function HomePage() {
isVerifiedOrganizer={
trip.organizer.organizerVerification?.status === "APPROVED"
}
participants={mapParticipants(trip)}
viewerInterests={viewerInterests}
priority={i === 0}
/>
))}
@@ -261,6 +287,7 @@ export default async function HomePage() {
id={trip.id}
title={trip.title}
category={trip.category}
vibe={trip.vibe}
destination={trip.destination}
location={trip.location}
date={trip.date}
@@ -274,35 +301,38 @@ export default async function HomePage() {
isVerifiedOrganizer={
trip.organizer.organizerVerification?.status === "APPROVED"
}
participants={mapParticipants(trip)}
viewerInterests={viewerInterests}
/>
))}
</div>
)}
</section>
{/* Budget Friendly */}
{budgetTrips.length > 0 && (
{/* Lagi Ramai — social proof, bukan price proof */}
{buzzingTrips.length > 0 && (
<section>
<div className="mb-4 flex items-center gap-3 sm:mb-5">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary-100 text-base sm:h-9 sm:w-9 sm:text-lg">
💸
🤝
</div>
<div>
<h2 className="text-base font-bold text-neutral-800 sm:text-lg">
Budget Friendly
Lagi Ramai
</h2>
<p className="text-[11px] text-neutral-500 sm:text-xs">
Trip di bawah Rp 300.000
Banyak yang sudah gabung kamu nggak bakal jalan sendirian
</p>
</div>
</div>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{budgetTrips.map((trip) => (
{buzzingTrips.map((trip) => (
<TripCard
key={trip.id}
id={trip.id}
title={trip.title}
category={trip.category}
vibe={trip.vibe}
destination={trip.destination}
location={trip.location}
date={trip.date}
@@ -316,6 +346,8 @@ export default async function HomePage() {
isVerifiedOrganizer={
trip.organizer.organizerVerification?.status === "APPROVED"
}
participants={mapParticipants(trip)}
viewerInterests={viewerInterests}
/>
))}
</div>