114 lines
3.7 KiB
TypeScript
114 lines
3.7 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import { FireIcon, SparklesIcon } from "@heroicons/react/24/outline";
|
||
|
|
import { useParams } from "next/navigation";
|
||
|
|
import { DataPageLayout } from "@/app/_components/data-page-layout";
|
||
|
|
import { trpc } from "@/lib/trpc";
|
||
|
|
|
||
|
|
export default function PublicProfilePage() {
|
||
|
|
const params = useParams<{ handle: string }>();
|
||
|
|
const handleSlug = decodeURIComponent(params.handle ?? "");
|
||
|
|
|
||
|
|
const profileQuery = trpc.users.publicProfileByHandle.useQuery(
|
||
|
|
{ handle: handleSlug },
|
||
|
|
{ retry: false },
|
||
|
|
);
|
||
|
|
|
||
|
|
if (profileQuery.isLoading) {
|
||
|
|
return (
|
||
|
|
<DataPageLayout title="Profil">
|
||
|
|
<p className="text-sm text-gray-500">Profil wird geladen …</p>
|
||
|
|
</DataPageLayout>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (profileQuery.isError || !profileQuery.data) {
|
||
|
|
return (
|
||
|
|
<DataPageLayout title="Profil nicht gefunden">
|
||
|
|
<div className="rounded-xl border border-gray-200 bg-white p-6 text-sm text-gray-600">
|
||
|
|
Dieses Profil gibt es nicht oder es ist privat. Nur Mitglieder, die ihr
|
||
|
|
Profil öffentlich geschaltet haben, sind hier sichtbar.
|
||
|
|
</div>
|
||
|
|
</DataPageLayout>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const profile = profileQuery.data;
|
||
|
|
const initial = (profile.username ?? "?").charAt(0).toUpperCase();
|
||
|
|
|
||
|
|
return (
|
||
|
|
<DataPageLayout title={profile.handle ?? profile.username ?? "Profil"}>
|
||
|
|
<div className="space-y-4">
|
||
|
|
<div className="flex items-center gap-4 rounded-xl border border-gray-200 bg-white p-5">
|
||
|
|
{profile.image ? (
|
||
|
|
// eslint-disable-next-line @next/next/no-img-element
|
||
|
|
<img
|
||
|
|
src={profile.image}
|
||
|
|
alt={profile.username ?? "Avatar"}
|
||
|
|
className="h-16 w-16 rounded-full object-cover"
|
||
|
|
/>
|
||
|
|
) : (
|
||
|
|
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-[#0F766E] text-xl font-bold text-white">
|
||
|
|
{initial}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
<div className="min-w-0">
|
||
|
|
<p className="truncate text-lg font-semibold text-gray-900">
|
||
|
|
{profile.username}
|
||
|
|
<span className="font-normal text-gray-400">#{profile.discriminator}</span>
|
||
|
|
</p>
|
||
|
|
{profile.bio ? (
|
||
|
|
<p className="mt-1 whitespace-pre-line text-sm text-gray-600">{profile.bio}</p>
|
||
|
|
) : (
|
||
|
|
<p className="mt-1 text-sm text-gray-400">Keine Bio hinterlegt.</p>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
||
|
|
<StatCard
|
||
|
|
icon={<FireIcon className="h-5 w-5 text-[#0F766E]" />}
|
||
|
|
label="Punkte gesamt"
|
||
|
|
value={profile.points.total.toLocaleString("de-DE")}
|
||
|
|
/>
|
||
|
|
<StatCard
|
||
|
|
icon={<FireIcon className="h-5 w-5 text-[#0F766E]" />}
|
||
|
|
label="Punkte (30 Tage)"
|
||
|
|
value={profile.points.last30Days.toLocaleString("de-DE")}
|
||
|
|
/>
|
||
|
|
<StatCard
|
||
|
|
icon={<SparklesIcon className="h-5 w-5 text-[#0F766E]" />}
|
||
|
|
label="Aktive Tage (7 Tage)"
|
||
|
|
value={`${profile.activeDaysLast7}/7`}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<p className="px-1 text-xs text-gray-400">
|
||
|
|
Gesundheitsdaten wie Gewicht, Kalorien und Ziele sind immer privat und
|
||
|
|
werden hier nie angezeigt.
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</DataPageLayout>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
function StatCard({
|
||
|
|
icon,
|
||
|
|
label,
|
||
|
|
value,
|
||
|
|
}: {
|
||
|
|
icon: React.ReactNode;
|
||
|
|
label: string;
|
||
|
|
value: string;
|
||
|
|
}) {
|
||
|
|
return (
|
||
|
|
<div className="rounded-xl border border-gray-200 bg-white p-4">
|
||
|
|
<div className="flex items-center gap-2 text-xs font-medium text-gray-500">
|
||
|
|
{icon}
|
||
|
|
{label}
|
||
|
|
</div>
|
||
|
|
<p className="mt-2 text-2xl font-bold text-gray-900">{value}</p>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|