refactor(ui): Improve layout and responsiveness in user profile and prompt pages

This commit is contained in:
Fatih Kadir Akın
2025-12-12 16:20:43 +03:00
parent 4a078c14c6
commit c5575fd2ef
5 changed files with 114 additions and 88 deletions
+68 -56
View File
@@ -290,67 +290,79 @@ export default async function UserProfilePage({ params, searchParams }: UserProf
return (
<div className="container py-6">
{/* Profile Header */}
<div className="flex flex-col md:flex-row items-start gap-6 mb-8">
<Avatar className="h-20 w-20">
<AvatarImage src={user.avatar || undefined} />
<AvatarFallback className="text-2xl">
{user.name?.charAt(0) || user.username.charAt(0)}
</AvatarFallback>
</Avatar>
<div className="flex-1">
<div className="flex items-start justify-between">
<div>
<div className="flex items-center gap-2 mb-1">
<h1 className="text-2xl font-bold">{user.name || user.username}</h1>
{user.verified && (
<BadgeCheck className="h-5 w-5 text-blue-500" />
)}
{user.role === "ADMIN" && (
<Badge variant="default" className="text-xs">Admin</Badge>
)}
</div>
<p className="text-muted-foreground text-sm mb-3 flex items-center gap-2">
@{user.username}
{isUnclaimed && (
<Badge variant="outline" className="text-[10px] text-amber-600 border-amber-500/30 bg-amber-500/10">
{t("unclaimedUser")}
</Badge>
)}
</p>
<div className="flex flex-col gap-4 mb-8">
{/* Avatar + Name row */}
<div className="flex items-center gap-4">
<Avatar className="h-16 w-16 md:h-20 md:w-20 shrink-0">
<AvatarImage src={user.avatar || undefined} />
<AvatarFallback className="text-xl md:text-2xl">
{user.name?.charAt(0) || user.username.charAt(0)}
</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2 flex-wrap">
<h1 className="text-xl md:text-2xl font-bold truncate">{user.name || user.username}</h1>
{user.verified && (
<BadgeCheck className="h-5 w-5 text-blue-500 shrink-0" />
)}
{user.role === "ADMIN" && (
<Badge variant="default" className="text-xs shrink-0">Admin</Badge>
)}
</div>
{isOwner && (
<Button variant="outline" size="sm" asChild>
<Link href="/settings">
<Settings className="h-4 w-4 mr-1.5" />
{t("editProfile")}
</Link>
</Button>
)}
<p className="text-muted-foreground text-sm flex items-center gap-2 flex-wrap">
@{user.username}
{isUnclaimed && (
<Badge variant="outline" className="text-[10px] text-amber-600 border-amber-500/30 bg-amber-500/10">
{t("unclaimedUser")}
</Badge>
)}
</p>
</div>
{/* Edit button - desktop only */}
{isOwner && (
<Button variant="outline" size="sm" asChild className="hidden md:inline-flex shrink-0">
<Link href="/settings">
<Settings className="h-4 w-4 mr-1.5" />
{t("editProfile")}
</Link>
</Button>
)}
</div>
{/* Stats */}
<div className="flex items-center gap-6 text-sm">
<div className="flex items-center gap-1.5">
<FileText className="h-4 w-4 text-muted-foreground" />
<span className="font-medium">{user._count.prompts}</span>
<span className="text-muted-foreground">{t("prompts").toLowerCase()}</span>
</div>
<div className="flex items-center gap-1.5">
<ArrowBigUp className="h-4 w-4 text-muted-foreground" />
<span className="font-medium">{totalUpvotes}</span>
<span className="text-muted-foreground">{t("upvotesReceived")}</span>
</div>
<div className="flex items-center gap-1.5">
<Users className="h-4 w-4 text-muted-foreground" />
<span className="font-medium">{user._count.contributions}</span>
<span className="text-muted-foreground">{t("contributionsCount")}</span>
</div>
<div className="flex items-center gap-1.5 text-muted-foreground">
<Calendar className="h-4 w-4" />
<span>{t("joined")} {formatDistanceToNow(user.createdAt, locale)}</span>
</div>
{/* Stats - stacked on mobile, inline on desktop */}
<div className="flex flex-col gap-2 md:flex-row md:items-center md:gap-6 text-sm">
<div className="flex items-center gap-1.5">
<FileText className="h-4 w-4 text-muted-foreground" />
<span className="font-medium">{user._count.prompts}</span>
<span className="text-muted-foreground">{t("prompts").toLowerCase()}</span>
</div>
<div className="flex items-center gap-1.5">
<ArrowBigUp className="h-4 w-4 text-muted-foreground" />
<span className="font-medium">{totalUpvotes}</span>
<span className="text-muted-foreground">{t("upvotesReceived")}</span>
</div>
<div className="flex items-center gap-1.5">
<Users className="h-4 w-4 text-muted-foreground" />
<span className="font-medium">{user._count.contributions}</span>
<span className="text-muted-foreground">{t("contributionsCount")}</span>
</div>
<div className="flex items-center gap-1.5 text-muted-foreground">
<Calendar className="h-4 w-4" />
<span>{t("joined")} {formatDistanceToNow(user.createdAt, locale)}</span>
</div>
</div>
{/* Edit button - below stats on mobile */}
{isOwner && (
<div className="md:hidden">
<Button variant="outline" size="sm" asChild className="w-full">
<Link href="/settings">
<Settings className="h-4 w-4 mr-1.5" />
{t("editProfile")}
</Link>
</Button>
</div>
)}
</div>
{/* Tabs for Prompts and Change Requests */}
+39 -25
View File
@@ -299,33 +299,47 @@ export default async function PromptPage({ params }: PromptPageProps) {
{/* Content Tabs */}
<Tabs defaultValue="content">
<div className="flex items-center justify-between mb-4">
<TabsList>
<TabsTrigger value="content">{t("promptContent")}</TabsTrigger>
<TabsTrigger value="versions">
<History className="h-4 w-4 mr-1" />
{t("versions")}
</TabsTrigger>
{changeRequests.length > 0 && (
<TabsTrigger value="changes" className="gap-1">
<GitPullRequest className="h-4 w-4" />
{t("changeRequests")}
{pendingCount > 0 && (
<Badge variant="destructive" className="ml-1 h-5 min-w-5 px-1 text-xs">
{pendingCount}
</Badge>
)}
</TabsTrigger>
)}
</TabsList>
<div className="flex flex-col gap-3 mb-4">
{/* Propose changes button - on top on mobile */}
{!isOwner && session?.user && (
<Button asChild size="sm">
<Link href={`/prompts/${id}/changes/new`}>
<GitPullRequest className="h-4 w-4 mr-1.5" />
{t("createChangeRequest")}
</Link>
</Button>
<div className="md:hidden">
<Button asChild size="sm" className="w-full">
<Link href={`/prompts/${id}/changes/new`}>
<GitPullRequest className="h-4 w-4 mr-1.5" />
{t("createChangeRequest")}
</Link>
</Button>
</div>
)}
<div className="flex items-center justify-between">
<TabsList>
<TabsTrigger value="content">{t("promptContent")}</TabsTrigger>
<TabsTrigger value="versions">
<History className="h-4 w-4 mr-1" />
{t("versions")}
</TabsTrigger>
{changeRequests.length > 0 && (
<TabsTrigger value="changes" className="gap-1">
<GitPullRequest className="h-4 w-4" />
<span className="hidden sm:inline">{t("changeRequests")}</span>
{pendingCount > 0 && (
<Badge variant="destructive" className="ml-1 h-5 min-w-5 px-1 text-xs">
{pendingCount}
</Badge>
)}
</TabsTrigger>
)}
</TabsList>
{/* Propose changes button - inline on desktop */}
{!isOwner && session?.user && (
<Button asChild size="sm" className="hidden md:inline-flex">
<Link href={`/prompts/${id}/changes/new`}>
<GitPullRequest className="h-4 w-4 mr-1.5" />
{t("createChangeRequest")}
</Link>
</Button>
)}
</div>
</div>
<TabsContent value="content" className="space-y-4 mt-0">
+3 -3
View File
@@ -77,9 +77,9 @@ export function AddVersionForm({ promptId, currentContent }: AddVersionFormProps
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogTrigger asChild>
<Button variant="outline" size="sm">
<Plus className="h-4 w-4 mr-1.5" />
{t("newVersion")}
<Button variant="outline" size="sm" className="px-2 sm:px-3">
<Plus className="h-4 w-4 sm:mr-1.5" />
<span className="hidden sm:inline">{t("newVersion")}</span>
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl">
@@ -91,9 +91,9 @@ export function VersionCompareModal({ versions, currentContent, promptType, stru
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="outline" size="sm">
<GitCompare className="h-4 w-4 mr-1.5" />
{t("compare")}
<Button variant="outline" size="sm" className="px-2 sm:px-3">
<GitCompare className="h-4 w-4 sm:mr-1.5" />
<span className="hidden sm:inline">{t("compare")}</span>
</Button>
</DialogTrigger>
<DialogContent className="max-w-4xl max-h-[90vh] flex flex-col">
+1 -1
View File
@@ -26,7 +26,7 @@ function TabsList({
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
"bg-muted text-muted-foreground inline-flex h-9 w-full md:w-fit items-center justify-start md:justify-center rounded-lg p-[3px] overflow-x-auto scrollbar-none",
className
)}
{...props}