Skip to content

Commit d43657c

Browse files
committed
feat: enhance DevelopersPage and DeveloperProfilePage with tier progress tracking, improved UI elements, and additional data fetching for better user experience
1 parent 91f7e24 commit d43657c

4 files changed

Lines changed: 146 additions & 58 deletions

File tree

src/app/developers/[id]/page.tsx

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -91,21 +91,42 @@ function getNextTierProgress(
9191
plugins: number,
9292
) {
9393
const score = downloads * 0.6 + rating * plugins * 20;
94-
const tiers = [0, 500, 2000, 5000, 10000];
94+
const tiers = [
95+
{ score: 0, name: "Rising" },
96+
{ score: 500, name: "Pro" },
97+
{ score: 2000, name: "Expert" },
98+
{ score: 5000, name: "Master" },
99+
{ score: 10000, name: "Legend" }
100+
];
95101

96-
let currentTier = 0;
102+
let currentTierIndex = 0;
97103
for (let i = 0; i < tiers.length; i++) {
98-
if (score >= tiers[i]!) currentTier = i;
104+
if (score >= tiers[i]!.score) currentTierIndex = i;
99105
}
100106

101-
if (currentTier === tiers.length - 1) return 100;
107+
if (currentTierIndex === tiers.length - 1) {
108+
return {
109+
progress: 100,
110+
currentTier: tiers[currentTierIndex]!,
111+
nextTier: null,
112+
currentScore: score,
113+
scoreNeeded: 0,
114+
};
115+
}
102116

103-
const currentTierScore = tiers[currentTier]!;
104-
const nextTierScore = tiers[currentTier + 1]!;
105-
const current = score - currentTierScore;
106-
const next = nextTierScore - currentTierScore;
117+
const currentTier = tiers[currentTierIndex]!;
118+
const nextTier = tiers[currentTierIndex + 1]!;
119+
const current = score - currentTier.score;
120+
const next = nextTier.score - currentTier.score;
121+
const progress = Math.min((current / next) * 100, 100);
107122

108-
return Math.min((current / next) * 100, 100);
123+
return {
124+
progress,
125+
currentTier,
126+
nextTier,
127+
currentScore: score,
128+
scoreNeeded: nextTier.score - score,
129+
};
109130
}
110131

111132
export default function DeveloperProfilePage() {
@@ -205,7 +226,7 @@ export default function DeveloperProfilePage() {
205226
stats?.averageRating || 0,
206227
stats?.totalPlugins || 0,
207228
);
208-
const progress = getNextTierProgress(
229+
const tierProgress = getNextTierProgress(
209230
stats?.totalDownloads || 0,
210231
stats?.averageRating || 0,
211232
stats?.totalPlugins || 0,
@@ -297,7 +318,7 @@ export default function DeveloperProfilePage() {
297318
<div className="mb-6 flex flex-wrap items-center justify-center gap-3 md:justify-start">
298319
<Badge
299320
className={cn(
300-
"border-0 bg-gradient-to-r px-4 py-2 text-sm text-white",
321+
"border-0 bg-gradient-to-r px-4 py-2 text-sm text-white shadow-lg",
301322
tier.color,
302323
)}
303324
>
@@ -341,25 +362,35 @@ export default function DeveloperProfilePage() {
341362
</div>
342363
<div className="text-center">
343364
<div className="mb-1 font-bold text-3xl text-primary">
344-
{Math.round(progress)}%
365+
{Math.round(tierProgress.progress)}%
345366
</div>
346367
<div className="text-muted-foreground text-sm">
347368
До {tier.name === "Legend" ? "Максимум" : "повышения"}
348369
</div>
349370
</div>
350371
</div>
351372

352-
{progress < 100 && tier.name !== "Legend" && (
373+
{tierProgress.progress < 100 && tier.name !== "Legend" && (
353374
<div className="mt-6">
354375
<div className="mb-2 flex items-center justify-between text-sm">
355376
<span className="text-muted-foreground">
356-
Прогресс до следующего ранга
377+
{tierProgress.nextTier ? `Прогресс до ${tierProgress.nextTier.name}` : "Прогресс"}
357378
</span>
358379
<span className="font-medium">
359-
{Math.round(progress)}%
380+
{Math.round(tierProgress.progress)}%
360381
</span>
361382
</div>
362-
<Progress value={progress} className="h-3" />
383+
<Progress value={tierProgress.progress} className="h-3 bg-muted/30" />
384+
{tierProgress.nextTier && (
385+
<div className="mt-2 text-center">
386+
<div className="text-xs text-muted-foreground">
387+
Нужно еще <span className="font-medium text-primary">{Math.ceil(tierProgress.scoreNeeded)}</span> очков для ранга <span className="font-medium">{tierProgress.nextTier.name}</span>
388+
</div>
389+
<div className="mt-1 text-xs text-muted-foreground">
390+
Текущий счет: <span className="font-medium">{Math.floor(tierProgress.currentScore)}</span>
391+
</div>
392+
</div>
393+
)}
363394
</div>
364395
)}
365396
</div>

src/app/developers/page.tsx

Lines changed: 97 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
CardTitle,
3333
} from "~/components/ui/card";
3434
import { Input } from "~/components/ui/input";
35+
import { Progress } from "~/components/ui/progress";
3536
import { Skeleton } from "~/components/ui/skeleton";
3637
import { cn, formatNumber } from "~/lib/utils";
3738
import { api } from "~/trpc/react";
@@ -44,25 +45,59 @@ function getDeveloperTier(downloads: number, rating: number, plugins: number) {
4445
name: "Legend",
4546
color: "from-yellow-400 to-orange-500",
4647
icon: Crown,
48+
bgColor: "from-yellow-50 to-orange-50 dark:from-yellow-900/20 dark:to-orange-900/20",
4749
};
4850
if (score >= 5000)
4951
return {
5052
name: "Master",
5153
color: "from-purple-400 to-pink-500",
5254
icon: Trophy,
55+
bgColor: "from-purple-50 to-pink-50 dark:from-purple-900/20 dark:to-pink-900/20",
5356
};
5457
if (score >= 2000)
55-
return { name: "Expert", color: "from-blue-400 to-cyan-500", icon: Award };
58+
return {
59+
name: "Expert",
60+
color: "from-blue-400 to-cyan-500",
61+
icon: Award,
62+
bgColor: "from-blue-50 to-cyan-50 dark:from-blue-900/20 dark:to-cyan-900/20",
63+
};
5664
if (score >= 500)
5765
return {
5866
name: "Pro",
5967
color: "from-green-400 to-emerald-500",
6068
icon: Target,
69+
bgColor: "from-green-50 to-emerald-50 dark:from-green-900/20 dark:to-emerald-900/20",
6170
};
6271
return {
6372
name: "Rising",
6473
color: "from-gray-400 to-slate-500",
6574
icon: Sparkles,
75+
bgColor: "from-gray-50 to-slate-50 dark:from-gray-900/20 dark:to-slate-900/20",
76+
};
77+
}
78+
79+
function getTierProgress(downloads: number, rating: number, plugins: number) {
80+
const score = downloads * 0.6 + rating * plugins * 20;
81+
const tiers = [0, 500, 2000, 5000, 10000];
82+
const tierNames = ["Rising", "Pro", "Expert", "Master", "Legend"];
83+
84+
let currentTier = 0;
85+
for (let i = 0; i < tiers.length; i++) {
86+
if (score >= tiers[i]!) currentTier = i;
87+
}
88+
89+
if (currentTier === tiers.length - 1) return { progress: 100, nextTier: null };
90+
91+
const currentTierScore = tiers[currentTier]!;
92+
const nextTierScore = tiers[currentTier + 1]!;
93+
const current = score - currentTierScore;
94+
const next = nextTierScore - currentTierScore;
95+
const progress = Math.min((current / next) * 100, 100);
96+
97+
return {
98+
progress,
99+
nextTier: tierNames[currentTier + 1] || null,
100+
scoreNeeded: nextTierScore - score
66101
};
67102
}
68103

@@ -162,87 +197,108 @@ export default function DevelopersPage() {
162197
) : (
163198
<>
164199
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
165-
{filteredDevelopers.map((developer: any) => {
200+
{filteredDevelopers.map((developer: any) => {
166201
const tier = getDeveloperTier(
167202
developer.totalDownloads || 0,
168203
developer.averageRating || 0,
169204
developer.pluginCount || 0,
170205
);
206+
const progress = getTierProgress(
207+
developer.totalDownloads || 0,
208+
developer.averageRating || 0,
209+
developer.pluginCount || 0,
210+
);
171211
const TierIcon = tier.icon;
172212

173213
return (
174214
<Card
175215
key={developer.id}
176-
className="group hover:-translate-y-1 h-full cursor-pointer overflow-hidden transition-all duration-200 hover:shadow-lg"
216+
className="group relative h-full cursor-pointer overflow-hidden bg-gradient-to-br from-card to-card/50 transition-all duration-300 hover:-translate-y-1 hover:shadow-xl"
217+
onClick={() =>
218+
(window.location.href = `/developers/${developer.id}`)
219+
}
177220
>
178-
<CardContent
179-
className="p-6"
180-
onClick={() =>
181-
(window.location.href = `/developers/${developer.id}`)
182-
}
183-
>
184-
<div className="flex items-start gap-4">
221+
{/* Tier accent border */}
222+
<div className={cn(
223+
"absolute inset-x-0 top-0 h-1 bg-gradient-to-r",
224+
tier.color
225+
)} />
226+
227+
<CardContent className="relative p-6 h-full flex flex-col">
228+
{/* Header with avatar and tier */}
229+
<div className="flex items-start gap-4 mb-4">
185230
<div className="relative">
186-
<Avatar className="h-12 w-12">
231+
<Avatar className="h-14 w-14 border-2 border-background shadow-lg">
187232
<AvatarImage
188233
src={developer.image || undefined}
189234
alt={developer.name || ""}
190235
className="object-cover"
191236
/>
192-
<AvatarFallback className="bg-primary/10 font-medium text-sm">
237+
<AvatarFallback className={cn(
238+
"bg-gradient-to-br font-medium text-sm text-white",
239+
tier.color
240+
)}>
193241
{(developer.name || "??")
194242
.slice(0, 2)
195243
.toUpperCase()}
196244
</AvatarFallback>
197245
</Avatar>
198246
<div
199247
className={cn(
200-
"-bottom-1 -right-1 absolute flex h-6 w-6 items-center justify-center rounded-full bg-gradient-to-r shadow-sm",
248+
"-bottom-1 -right-1 absolute flex h-7 w-7 items-center justify-center rounded-full bg-gradient-to-r shadow-lg border-2 border-background",
201249
tier.color,
202250
)}
203251
>
204-
<TierIcon className="h-3 w-3 text-white" />
252+
<TierIcon className="h-3.5 w-3.5 text-white" />
205253
</div>
206254
</div>
207255

208256
<div className="min-w-0 flex-1">
209-
<h3 className="truncate font-semibold transition-colors group-hover:text-primary">
257+
<h3 className="truncate font-bold text-lg transition-colors group-hover:text-primary">
210258
{developer.name || "Anonymous"}
211259
</h3>
212-
<div className="mt-1 flex items-center gap-2">
260+
<div className="mt-2">
213261
<Badge
214-
variant="secondary"
215262
className={cn(
216-
"border-0 bg-gradient-to-r text-white text-xs",
263+
"border-0 bg-gradient-to-r text-white text-xs shadow-md px-3 py-1",
217264
tier.color,
218265
)}
219266
>
220-
<TierIcon className="mr-1 h-2.5 w-2.5" />
221-
{tier.name}
267+
<TierIcon className="mr-1.5 h-3 w-3" />
268+
{tier.name} Developer
222269
</Badge>
223-
{developer.isVerified && (
224-
<Badge
225-
variant="outline"
226-
className="border-blue-500 text-blue-600 text-xs"
227-
>
228-
<Zap className="mr-1 h-2.5 w-2.5" />
229-
Verified
230-
</Badge>
231-
)}
232270
</div>
233271
</div>
234272
</div>
235273

236274
{developer.bio && (
237-
<p className="mt-4 line-clamp-2 text-muted-foreground text-sm">
275+
<p className="mb-4 line-clamp-2 text-muted-foreground text-sm">
238276
{developer.bio}
239277
</p>
240278
)}
241279

242-
<div className="mt-4 grid grid-cols-3 gap-2 text-center">
243-
<div className="rounded-lg bg-muted/50 p-2">
280+
{/* Progress to next tier */}
281+
{progress.nextTier && progress.progress < 100 && (
282+
<div className="mb-4">
283+
<div className="mb-2 flex items-center justify-between text-xs">
284+
<span className="text-muted-foreground">
285+
Прогресс до {progress.nextTier}
286+
</span>
287+
<span className="font-medium text-primary">
288+
{Math.round(progress.progress)}%
289+
</span>
290+
</div>
291+
<Progress
292+
value={progress.progress}
293+
className="h-1.5"
294+
/>
295+
</div>
296+
)}
297+
298+
<div className="mb-4 grid grid-cols-3 gap-2 text-center">
299+
<div className="rounded-lg bg-muted/30 p-2 transition-colors group-hover:bg-muted/50">
244300
<div className="flex items-center justify-center gap-1">
245-
<Package className="h-3 w-3 text-muted-foreground" />
301+
<Package className="h-3 w-3 text-blue-600" />
246302
<span className="font-medium text-sm">
247303
{developer.pluginCount || 0}
248304
</span>
@@ -252,9 +308,9 @@ export default function DevelopersPage() {
252308
</div>
253309
</div>
254310

255-
<div className="rounded-lg bg-muted/50 p-2">
311+
<div className="rounded-lg bg-muted/30 p-2 transition-colors group-hover:bg-muted/50">
256312
<div className="flex items-center justify-center gap-1">
257-
<Download className="h-3 w-3 text-muted-foreground" />
313+
<Download className="h-3 w-3 text-green-600" />
258314
<span className="font-medium text-sm">
259315
{formatNumber(developer.totalDownloads || 0)}
260316
</span>
@@ -264,9 +320,9 @@ export default function DevelopersPage() {
264320
</div>
265321
</div>
266322

267-
<div className="rounded-lg bg-muted/50 p-2">
323+
<div className="rounded-lg bg-muted/30 p-2 transition-colors group-hover:bg-muted/50">
268324
<div className="flex items-center justify-center gap-1">
269-
<Star className="h-3 w-3 text-muted-foreground" />
325+
<Star className="h-3 w-3 text-yellow-600" />
270326
<span className="font-medium text-sm">
271327
{developer.averageRating?.toFixed(1) || "0.0"}
272328
</span>
@@ -277,13 +333,13 @@ export default function DevelopersPage() {
277333
</div>
278334
</div>
279335

280-
<div className="mt-4 flex items-center justify-between">
336+
<div className="mt-auto flex items-center justify-between">
281337
<div className="flex items-center gap-1">
282338
{developer.githubUsername && (
283339
<Button
284340
variant="ghost"
285341
size="sm"
286-
className="h-8 w-8 p-0"
342+
className="h-8 w-8 p-0 hover:bg-primary/10 transition-colors"
287343
onClick={(e) => {
288344
e.stopPropagation();
289345
window.open(
@@ -299,7 +355,7 @@ export default function DevelopersPage() {
299355
<Button
300356
variant="ghost"
301357
size="sm"
302-
className="h-8 w-8 p-0"
358+
className="h-8 w-8 p-0 hover:bg-primary/10 transition-colors"
303359
onClick={(e) => {
304360
e.stopPropagation();
305361
window.open(developer.website, "_blank");
@@ -313,7 +369,7 @@ export default function DevelopersPage() {
313369
<Button
314370
size="sm"
315371
variant="ghost"
316-
className="h-8 w-8 p-0"
372+
className="h-8 w-8 p-0 hover:bg-primary/10 transition-colors opacity-60 group-hover:opacity-100"
317373
>
318374
<ExternalLink className="h-4 w-4" />
319375
</Button>

src/components/ui/card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
77
<div
88
data-slot="card"
99
className={cn(
10-
"flex flex-col gap-4 rounded-xl border bg-card py-3 text-card-foreground shadow-sm sm:gap-6 sm:py-6",
10+
"flex flex-col gap-4 rounded-xl border bg-card py-3 text-card-foreground sm:gap-6 sm:py-6",
1111
className,
1212
)}
1313
{...props}

src/server/api/routers/developers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const developersRouter = createTRPCRouter({
3333
isVerified: users.isVerified,
3434
pluginCount: count(plugins.id),
3535
averageRating: sql<number>`AVG(${plugins.rating})`,
36+
totalDownloads: sql<number>`SUM(${plugins.downloadCount})`,
3637
})
3738
.from(users)
3839
.innerJoin(plugins, eq(plugins.authorId, users.id))

0 commit comments

Comments
 (0)