feat: add profile rank badges and guide
This commit is contained in:
@@ -5,17 +5,18 @@ import Image from "next/image";
|
||||
import { CalendarDays, EyeOff, UserRound } from "lucide-react";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { PublicProfileBadges } from "@/components/profile/PublicProfileBadges";
|
||||
import { Helmet } from "@/lib/helmet";
|
||||
import { Link, useParams } from "@/lib/router";
|
||||
import { api } from "@/lib/api";
|
||||
import { formatJalali, formatNumberPersian, toPersianDigits } from "@/lib/utils";
|
||||
import { formatJalali, toPersianDigits } from "@/lib/utils";
|
||||
import type { PublicProfileEventSchema } from "@/lib/types";
|
||||
|
||||
function EventCard({ event }: { event: PublicProfileEventSchema }) {
|
||||
return (
|
||||
<Link to={`/events/${event.slug}`} className="group flex min-h-28 gap-4 rounded-2xl border border-border/70 bg-background p-3 text-right transition-colors hover:bg-muted/45 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
||||
{event.image_url ? <Image src={event.image_url} alt="" width={160} height={120} unoptimized className="aspect-[4/3] w-24 shrink-0 rounded-xl object-cover sm:w-32" /> : (
|
||||
<span className="flex aspect-[4/3] w-24 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary sm:w-32"><CalendarDays className="h-6 w-6" aria-hidden="true" /></span>
|
||||
<Link to={`/events/${event.slug}`} className="group flex min-h-28 flex-col gap-3 rounded-2xl border border-border/70 bg-background p-3 text-right transition-colors hover:bg-muted/45 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring sm:flex-row sm:gap-4">
|
||||
{event.image_url ? <Image src={event.image_url} alt="" width={640} height={360} unoptimized className="aspect-video w-full shrink-0 rounded-xl object-cover sm:w-32" /> : (
|
||||
<span className="flex aspect-video w-full shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary sm:w-32"><CalendarDays className="h-6 w-6" aria-hidden="true" /></span>
|
||||
)}
|
||||
<span className="flex min-w-0 flex-1 flex-col gap-2 py-1">
|
||||
<span className="font-semibold text-foreground group-hover:text-primary">{event.title}</span>
|
||||
@@ -53,11 +54,12 @@ export default function PublicProfile() {
|
||||
|
||||
const fallback = `${profile.first_name?.[0] ?? ""}${profile.last_name?.[0] ?? ""}` || profile.username[0];
|
||||
const maskedDetails = [
|
||||
{ label: "شماره موبایل", value: profile.mobile },
|
||||
{ label: "شماره دانشجویی", value: profile.student_id },
|
||||
{ label: "دانشگاه", value: profile.university },
|
||||
{ label: "رشته", value: profile.major },
|
||||
{ label: "سال ورود", value: profile.year_of_study || null },
|
||||
{ label: "شماره موبایل", value: profile.mobile, isEmail: false },
|
||||
...(profile.email ? [{ label: "ایمیل", value: profile.email, isEmail: true }] : []),
|
||||
{ label: "دانشگاه", value: profile.university, isEmail: false },
|
||||
{ label: "رشته", value: profile.major, isEmail: false },
|
||||
{ label: "شماره دانشجویی", value: profile.student_id, isEmail: false },
|
||||
{ label: "سال ورود", value: profile.year_of_study || null, isEmail: false },
|
||||
];
|
||||
const pageDescription = profile.bio || `پروفایل عمومی ${profile.full_name} و رویدادهای او در انجمن علمی مهندسی کامپیوتر.`;
|
||||
|
||||
@@ -85,25 +87,11 @@ export default function PublicProfile() {
|
||||
</div>
|
||||
</div>
|
||||
{profile.bio ? <p className="mt-5 whitespace-pre-wrap text-sm leading-7 text-muted-foreground">{profile.bio}</p> : null}
|
||||
<div className="mt-5 grid grid-cols-1 gap-3">
|
||||
<div className="rounded-2xl border border-border/70 bg-muted/45 p-4 text-center shadow-sm dark:bg-background/70"><p className="text-xl font-bold">{formatNumberPersian(profile.events.length)}</p><p className="mt-1 text-xs text-muted-foreground">رویداد</p></div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-border/70 shadow-sm">
|
||||
<CardHeader className="pb-2 text-right"><CardTitle className="text-base">{profile.details_visible ? "اطلاعات بیشتر" : "اطلاعات خصوصی"}</CardTitle></CardHeader>
|
||||
<CardContent className="space-y-1">
|
||||
{profile.details_visible ? maskedDetails.map(({ label, value }) => (
|
||||
<div key={label} className="flex items-center justify-between gap-4 border-b border-border/60 py-3 last:border-0 text-sm">
|
||||
<span className="shrink-0 text-muted-foreground">{label}</span>
|
||||
<span className="text-left font-medium">{value ? toPersianDigits(String(value)) : "ثبت نشده"}</span>
|
||||
</div>
|
||||
)) : (
|
||||
<div className="relative isolate overflow-hidden rounded-xl border border-border/70 bg-muted/20 p-5 sm:p-6">
|
||||
<div aria-hidden="true" className="pointer-events-none select-none space-y-3 opacity-55 blur-[4px]">
|
||||
{!profile.details_visible ? (
|
||||
<div className="relative isolate mt-5 overflow-hidden rounded-xl border border-border/70 bg-muted/20 p-4 sm:p-5">
|
||||
<div aria-hidden="true" className="pointer-events-none select-none space-y-2.5 opacity-55 blur-[4px]">
|
||||
{maskedDetails.map(({ label }) => (
|
||||
<div key={label} className="flex items-center justify-between gap-4 border-b border-border/60 py-3 last:border-0 text-sm">
|
||||
<div key={label} className="flex items-center justify-between gap-4 border-b border-border/60 py-2.5 last:border-0 text-sm">
|
||||
<span className="shrink-0 text-muted-foreground">{label}</span>
|
||||
<span className="font-medium">••••••••</span>
|
||||
</div>
|
||||
@@ -111,17 +99,34 @@ export default function PublicProfile() {
|
||||
</div>
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-background/55 p-4 text-center">
|
||||
<div className="max-w-xs">
|
||||
<span className="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||||
<EyeOff className="h-6 w-6" aria-hidden="true" />
|
||||
<span className="mx-auto mb-2 flex h-11 w-11 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||||
<EyeOff className="h-5 w-5" aria-hidden="true" />
|
||||
</span>
|
||||
<p className="font-semibold">جزئیات این پروفایل خصوصی است</p>
|
||||
<p className="mt-1 text-sm leading-6 text-muted-foreground">نمایش اطلاعات تکمیلی به درخواست صاحب حساب غیرفعال شده است.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-5 space-y-1">
|
||||
{maskedDetails.map(({ label, value, isEmail }) => (
|
||||
<div key={label} className="flex items-center justify-between gap-4 border-b border-border/60 py-3 last:border-0 text-sm">
|
||||
<span className="shrink-0 text-muted-foreground">{label}</span>
|
||||
<span dir={isEmail ? "ltr" : undefined} className={`min-w-0 text-left font-medium ${isEmail ? "break-all" : ""}`}>
|
||||
{value ? (isEmail ? String(value) : toPersianDigits(String(value))) : "ثبت نشده"}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-border/70 shadow-sm">
|
||||
<CardContent className="pt-6">
|
||||
<PublicProfileBadges badges={profile.badges} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</aside>
|
||||
|
||||
<div className="space-y-5 lg:col-span-8">
|
||||
|
||||
Reference in New Issue
Block a user