Skip to content

Commit abf60ae

Browse files
authored
feat: [LAR-150] add forum leaderboard page (#289)
1 parent 5d1f6b9 commit abf60ae

29 files changed

+419
-166
lines changed

app/Livewire/Reactions.php renamed to app/Livewire/Components/Reactions.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
namespace App\Livewire;
5+
namespace App\Livewire\Components;
66

77
use App\Contracts\ReactableInterface;
88
use App\Models\Reaction;
@@ -40,6 +40,6 @@ public function userReacted(string $reaction): void
4040

4141
public function render(): View
4242
{
43-
return view('livewire.reactions');
43+
return view('livewire.components.reactions');
4444
}
4545
}

app/Livewire/ReportSpam.php renamed to app/Livewire/Components/ReportSpam.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
namespace App\Livewire;
5+
namespace App\Livewire\Components;
66

77
use App\Actions\ReportSpamAction;
88
use App\Contracts\SpamReportableContract;

app/Livewire/Pages/Forum/Index.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ final class Index extends Component
2424
use WithoutUrlPagination;
2525
use WithPagination;
2626

27-
#[Url(as: 'channel')]
27+
#[Url]
2828
public ?string $channel = null;
2929

30-
#[Url(as: 'solved')]
30+
#[Url]
3131
public ?string $solved = null;
3232

3333
#[Url(as: 'me')]
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Livewire\Pages\Forum;
6+
7+
use App\Models\User;
8+
use Illuminate\Contracts\View\View;
9+
use Illuminate\Database\Eloquent\Collection;
10+
use Illuminate\Support\Facades\Cache;
11+
use Livewire\Attributes\Layout;
12+
use Livewire\Component;
13+
14+
#[Layout('layouts.forum')]
15+
final class Leaderboard extends Component
16+
{
17+
public function render(): View
18+
{
19+
$startPosition = 1;
20+
$leaders = collect();
21+
22+
/** @var Collection $leaderboard */
23+
$leaderboard = User::mostSolutionsInLastDays(365)
24+
->take(30)
25+
->get()
26+
->reject(fn ($leaderboard) => $leaderboard->solutions_count === 0); // @phpstan-ignore-line
27+
28+
if ($leaderboard->count() > 3) {
29+
$leaders = $leaderboard->slice(0, 3);
30+
$startPosition = 4;
31+
}
32+
33+
return view('livewire.pages.forum.leaderboard', [
34+
'members' => Cache::remember(
35+
key: 'members',
36+
ttl: now()->addWeek(),
37+
callback: fn () => $leaderboard->reject(
38+
fn (User $user) => in_array($user->id, $leaders->pluck('id')->toArray()) // @phpstan-ignore-line
39+
)
40+
),
41+
'leaders' => Cache::remember(
42+
key: 'leaders',
43+
ttl: now()->addWeek(),
44+
callback: fn () => $leaders
45+
),
46+
'startPosition' => $startPosition,
47+
]);
48+
}
49+
}

app/Models/User.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ public function countThreads(): int
420420
public function scopeMostSolutions(Builder $query, ?int $inLastDays = null): Builder
421421
{
422422
return $query->withCount(['replyAble as solutions_count' => function ($query) use ($inLastDays) {
423-
$query->where('replyable_type', 'threads')
423+
$query->where('replyable_type', 'thread')
424424
->join('threads', 'threads.solution_reply_id', '=', 'replies.id');
425425

426426
if ($inLastDays) {

app/View/Composers/TopMembersComposer.php

Lines changed: 0 additions & 24 deletions
This file was deleted.

lang/en/global.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,13 @@
107107
'language' => 'Language',
108108
'french' => 'French',
109109
'english' => 'English',
110+
'experience' => 'Experience',
111+
'last_active' => 'Last active',
112+
'first_place' => '1st place',
113+
'second_place' => '2nd place',
114+
'third_place' => '3rd place',
115+
'ranking_updated' => 'The rankings are updated weekly.',
116+
'place' => 'Place',
117+
'user' => 'User',
110118

111119
];

lang/en/pages/forum.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,6 @@
4747
'prevent_text_one' => 'Make sure you\'ve read our',
4848
'rules' => 'rules of conduct',
4949
'prevent_text_two' => 'before replying to this thread.',
50+
'leaderboard_empty' => 'No ranking available',
5051

5152
];

lang/fr/global.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,13 @@
107107
'language' => 'Langue',
108108
'french' => 'Français',
109109
'english' => 'Anglais',
110+
'experience' => 'Expérience',
111+
'last_active' => 'Dernière activité',
112+
'first_place' => '1ere place',
113+
'second_place' => '2e place',
114+
'third_place' => '3e place',
115+
'ranking_updated' => 'Ce classement est mis à jour toutes les semaines.',
116+
'place' => 'Position',
117+
'user' => 'Utilisateur',
118+
110119
];

lang/fr/pages/forum.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,6 @@
4747
'prevent_text_one' => 'Assurez-vous d\'avoir lu nos',
4848
'rules' => 'règles de conduite',
4949
'prevent_text_two' => 'avant de répondre à ce thread.',
50+
'leaderboard_empty' => 'Aucun classement disponible',
5051

5152
];

resources/css/app.css

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,15 @@
66
@import 'tag.css';
77
@import 'forms.css';
88
@import 'torchlight.css';
9-
@import 'toc.css';
9+
@import 'forum.css';
1010
@import 'header.css';
1111

12+
@property --border-angle {
13+
inherits: false;
14+
initial-value: 0deg;
15+
syntax: '<angle>';
16+
}
17+
1218
:root {
1319
--laravel: #F56857;
1420
--livewire: #fb70a9;

resources/css/file-upload.css

Lines changed: 0 additions & 69 deletions
This file was deleted.

resources/css/forum.css

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#leaderboard {
2+
*, *::before, *::after {
3+
box-sizing: border-box;
4+
transform-style: preserve-3d;
5+
}
6+
7+
.leaderboard {
8+
place-items: center;
9+
transform: perspective(400px);
10+
transform-style: preserve-3d;
11+
}
12+
13+
.leaderboard > .stage {
14+
@apply relative w-full rounded-t-lg;
15+
transform: rotateX(70deg);
16+
}
17+
18+
.leaderboard > .stage > .stage-front {
19+
@apply absolute w-full rounded-b-lg origin-top top-full;
20+
transform: rotateX(-80deg);
21+
}
22+
}

resources/css/toc.css

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
@props([
2+
'user',
3+
'position',
4+
])
5+
6+
@php
7+
$icon = match ($position) {
8+
1 => 'icon.trophies.first',
9+
2 => 'icon.trophies.second',
10+
3 => 'icon.trophies.third',
11+
default => 'phosphor-trophy-duotone',
12+
};
13+
14+
$ranking = match ($position) {
15+
1 => __('global.first_place'),
16+
2 => __('global.second_place'),
17+
3 => __('global.third_place'),
18+
default => 'N/A',
19+
};
20+
21+
$color = match ($position) {
22+
1 => 'success',
23+
2 => 'warning',
24+
3 => 'danger',
25+
default => 'gray',
26+
};
27+
@endphp
28+
29+
<div class="bg-white divide-y divide-gray-200 rounded-lg ring-1 ring-gray-200 dark:bg-gray-800 dark:ring-white/10 dark:divide-white/10">
30+
<div class="flex gap-4 p-4">
31+
<div class="relative flex-1 flex items-center gap-2">
32+
<x-filament::badge :color="$color" class="absolute -top-7 !rounded-full">
33+
{{ $ranking }}
34+
</x-filament::badge>
35+
<x-user.avatar :user="$user" class="size-7" />
36+
<div class="text-sm truncate">
37+
<h5 class="font-medium text-gray-900 truncate dark:text-white">
38+
{{ $user->name }}
39+
</h5>
40+
<span class="text-gray-500 dark:text-gray-400">
41+
{{ '@' . $user->username }}
42+
</span>
43+
</div>
44+
</div>
45+
<div>
46+
<x-dynamic-component :component="$icon" class="size-10" aria-hidden="true" />
47+
</div>
48+
</div>
49+
<div class="flex divide-x divide-gray-200 dark:divide-white/10">
50+
<div class="flex flex-col px-4 py-2.5">
51+
<span class="text-xs/4 text-gray-400 capitalize dark:text-gray-500">
52+
{{ __('global.experience') }}
53+
</span>
54+
<span class="font-medium text-sm text-gray-700 dark:text-gray-300">
55+
{{ $user->getPoints() }}
56+
</span>
57+
</div>
58+
<div class="flex flex-col px-4 py-2.5">
59+
<span class="text-xs/4 text-gray-400 capitalize dark:text-gray-500">
60+
{{ __('global.answers') }}
61+
</span>
62+
<span class="font-medium text-sm text-gray-700 dark:text-gray-300">
63+
{{ $user->solutions_count }}
64+
</span>
65+
</div>
66+
<div @class([
67+
'flex flex-col px-4 py-2.5',
68+
'lg:hidden' => $position !== 1,
69+
])>
70+
<span class="text-xs/4 text-gray-400 capitalize dark:text-gray-500">
71+
{{ __('global.last_active') }}
72+
</span>
73+
74+
<span class="font-medium text-sm text-gray-700 dark:text-gray-300">
75+
{{ $user->last_active_at?->diffForHumans() }}
76+
</span>
77+
</div>
78+
</div>
79+
</div>

resources/views/components/forum/thread.blade.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class="inline-flex items-center rounded-xl gap-1 px-2 py-0.5 font-medium bg-prim
3636
</span>
3737
</x-link>
3838
<span class="flex items-center text-xs text-gray-500 flex-wrap gap-1 dark:text-gray-400">
39-
<span>{{ __('global.ask') }}</span>
39+
<span class="hidden lg:inline">{{ __('global.ask') }}</span>
4040
<time datetime="{{ $thread->created_at }}">
4141
{{ $thread->created_at->diffForHumans() }}
4242
</time>

0 commit comments

Comments
 (0)