refactor(ui): Improve layout and responsiveness in user profile and prompt pages
This commit is contained in:
+68
-56
@@ -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 */}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user