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
+102
View File
@@ -0,0 +1,102 @@
import Image from "next/image";
import Link from "next/link";
import { vibeMeta } from "@/lib/vibe";
import type { Vibe } from "@/app/generated/prisma/enums";
interface UserCardProps {
id: string;
name: string;
image: string | null;
isVerifiedOrganizer: boolean;
profile: {
bio: string | null;
city: string | null;
interests: string[];
vibe: Vibe | null;
} | null;
}
export function UserCard({
id,
name,
image,
isVerifiedOrganizer,
profile,
}: UserCardProps) {
const interests = profile?.interests ?? [];
return (
<Link
href={`/u/${id}`}
className="group flex h-full flex-col rounded-2xl border border-neutral-200 bg-white p-4 shadow-sm transition-all hover:-translate-y-0.5 hover:border-primary-300 hover:shadow-md"
>
<div className="flex items-start gap-3">
{image ? (
<Image
src={image}
alt=""
width={56}
height={56}
className="h-14 w-14 shrink-0 rounded-full object-cover"
/>
) : (
<div className="flex h-14 w-14 shrink-0 items-center justify-center rounded-full bg-primary-600 text-lg font-bold text-white">
{name.charAt(0).toUpperCase()}
</div>
)}
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-bold text-neutral-800 group-hover:text-primary-700">
{name}
</p>
{profile?.city && (
<p className="truncate text-[11px] text-neutral-500 sm:text-xs">
📍 {profile.city}
</p>
)}
<div className="mt-1 flex flex-wrap gap-1">
{isVerifiedOrganizer && (
<span
className="inline-flex items-center gap-0.5 rounded-full bg-primary-100 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-primary-800"
title="Organizer terverifikasi"
>
Organizer
</span>
)}
{profile?.vibe && (
<span
className="inline-flex items-center gap-0.5 rounded-full bg-secondary-100 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-secondary-800"
title={vibeMeta(profile.vibe).description}
>
<span aria-hidden>{vibeMeta(profile.vibe).icon}</span>
<span>{vibeMeta(profile.vibe).label}</span>
</span>
)}
</div>
</div>
</div>
{profile?.bio && (
<p className="mt-3 line-clamp-2 text-xs leading-relaxed text-neutral-600">
{profile.bio}
</p>
)}
{interests.length > 0 && (
<div className="mt-3 flex flex-wrap gap-1">
{interests.slice(0, 5).map((tag) => (
<span
key={tag}
className="rounded-full bg-secondary-50 px-2 py-0.5 text-[10px] font-medium text-secondary-700"
>
#{tag}
</span>
))}
{interests.length > 5 && (
<span className="text-[10px] text-neutral-400">
+{interests.length - 5}
</span>
)}
</div>
)}
</Link>
);
}