Skip to content

Commit f7f2216

Browse files
authored
Merge pull request #27 from laravelcm/user-dashboard
User dashboard
2 parents 3a3da41 + 1444614 commit f7f2216

29 files changed

+600
-41
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\User;
4+
5+
use App\Http\Controllers\Controller;
6+
use Illuminate\Support\Facades\Auth;
7+
8+
class DashboardController extends Controller
9+
{
10+
public function dashboard()
11+
{
12+
return view('user.dashboard', [
13+
'user' => $user = Auth::user(),
14+
'articles' => $user->articles()
15+
->orderByDesc('submitted_at')
16+
->orderByDesc('created_at')
17+
->paginate(5),
18+
]);
19+
}
20+
21+
public function threads()
22+
{
23+
return view('user.threads', [
24+
'user' => $user = Auth::user(),
25+
'threads' => $user->threads()
26+
->recent()
27+
->paginate(5),
28+
]);
29+
}
30+
31+
public function discussions()
32+
{
33+
return view('user.discussions', [
34+
'user' => $user = Auth::user(),
35+
'discussions' => $user->discussions()
36+
->orderByDesc('created_at')
37+
->paginate(5),
38+
]);
39+
}
40+
}

app/Http/Controllers/User/SettingController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public function update(UpdateProfileRequest $request)
3434
'bio' => trim(strip_tags($request->bio)),
3535
'twitter_profile' => $request->twitter_profile,
3636
'github_profile' => $request->github_profile,
37+
'linkedin_profile' => $request->linkedin_profile,
3738
'phone_number' => $request->phone_number,
3839
'location' => $request->location,
3940
'website' => $request->website,

app/Http/Livewire/Articles/Create.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,11 @@ public function store()
7070

7171
if ($this->submitted) {
7272
// Envoi du mail a l'admin pour la validation de l'article
73-
session()->flash('success', 'Merci d\'avoir soumis votre article. Vous aurez des nouvelles que lorsque nous accepterons votre article.');
73+
session()->flash('status', 'Merci d\'avoir soumis votre article. Vous aurez des nouvelles que lorsque nous accepterons votre article.');
7474
}
7575

7676
$user->hasRole('user') ?
77-
$this->redirect('/articles/me') :
77+
$this->redirectRoute('dashboard') :
7878
$this->redirectRoute('articles.show', $article);
7979
}
8080

app/Models/Discussion.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,12 @@ public function excerpt(int $limit = 110): string
121121

122122
public function isPinned(): bool
123123
{
124-
return (bool) $this->is_pinned;
124+
return $this->is_pinned;
125125
}
126126

127127
public function isLocked(): bool
128128
{
129-
return (bool) $this->locked;
129+
return $this->locked;
130130
}
131131

132132
public function getCountAllRepliesWithChildAttribute(): int

app/Models/User.php

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Illuminate\Foundation\Auth\User as Authenticatable;
1313
use Illuminate\Notifications\Notifiable;
1414
use Illuminate\Support\Facades\Auth;
15+
use Illuminate\Support\Facades\Cache;
1516
use Spatie\MediaLibrary\HasMedia;
1617
use Spatie\MediaLibrary\InteractsWithMedia;
1718
use Spatie\Permission\Traits\HasRoles;
@@ -42,6 +43,7 @@ class User extends Authenticatable implements MustVerifyEmail, HasMedia
4243
'phone_number',
4344
'github_profile',
4445
'twitter_profile',
46+
'linkedin_profile',
4547
'website',
4648
'last_login_at',
4749
'last_login_ip',
@@ -219,6 +221,16 @@ public function twitter(): ?string
219221
return $this->twitter_profile;
220222
}
221223

224+
public function hasTwitterAccount(): bool
225+
{
226+
return ! empty($this->twitter());
227+
}
228+
229+
public function linkedin(): ?string
230+
{
231+
return $this->linkedin_profile;
232+
}
233+
222234
public function scopeModerators(Builder $query): Builder
223235
{
224236
return $query->whereHas('roles', function ($query) {
@@ -286,19 +298,34 @@ public function routeNotificationForSlack($notification): string
286298
return env('SLACK_WEBHOOK_URL', '');
287299
}
288300

289-
public function countReplies(): int
301+
public function replies(): Collection
290302
{
291-
return $this->replyAble()->count();
303+
return $this->replyAble;
292304
}
293305

294-
public function replies(): Collection
306+
public function countReplies(): int
295307
{
296-
return $this->replyAble;
308+
return Cache::remember('replies_count', now()->addHours(2), fn () => $this->replyAble()->count());
297309
}
298310

299311
public function countSolutions(): int
300312
{
301-
return $this->replyAble()->isSolution()->count();
313+
return Cache::remember('solutions_count', now()->addHours(2), fn () => $this->replyAble()->isSolution()->count());
314+
}
315+
316+
public function countArticles(): int
317+
{
318+
return Cache::remember('articles_count', now()->addHours(2), fn () => $this->articles()->approved()->count());
319+
}
320+
321+
public function countDiscussions(): int
322+
{
323+
return Cache::remember('discussions_count', now()->addHours(2), fn () => $this->discussions()->count());
324+
}
325+
326+
public function countThreads(): int
327+
{
328+
return Cache::remember('threads_count', now()->addHours(2), fn () => $this->threads()->count());
302329
}
303330

304331
public function scopeMostSolutions(Builder $query, int $inLastDays = null)
@@ -339,6 +366,7 @@ public function scopeMostSubmissionsInLastDays(Builder $query, int $days)
339366
public function scopeWithCounts(Builder $query)
340367
{
341368
return $query->withCount([
369+
'articles as articles_count',
342370
'threads as threads_count',
343371
'replyAble as replies_count',
344372
'replyAble as solutions_count' => function (Builder $query) {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
class AddLinkedinProfileColumnToUsersTable extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*
12+
* @return void
13+
*/
14+
public function up()
15+
{
16+
Schema::table('users', function (Blueprint $table) {
17+
$table->string('linkedin_profile')
18+
->after('twitter_profile')
19+
->nullable();
20+
});
21+
}
22+
23+
/**
24+
* Reverse the migrations.
25+
*
26+
* @return void
27+
*/
28+
public function down()
29+
{
30+
Schema::table('users', function (Blueprint $table) {
31+
$table->dropColumn('linkedin_profile');
32+
});
33+
}
34+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"prettier-standard": "^16.4.1"
2525
},
2626
"dependencies": {
27+
"@alpinejs/intersect": "^3.6.1",
2728
"@headlessui/react": "^1.4.2",
2829
"@heroicons/react": "^1.0.5",
2930
"@tailwindcss/aspect-ratio": "^0.2.0",

public/css/app.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/js/app.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/mix-manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"/js/app.js": "/js/app.js?id=6e9d959a77c0ed56a844",
3-
"/css/app.css": "/css/app.css?id=a4b94cf149d9afd5d3e5"
2+
"/js/app.js": "/js/app.js?id=7d557689434ab2f122d8",
3+
"/css/app.css": "/css/app.css?id=fe258fa62dce97e1cd59"
44
}

resources/js/app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Alpine from 'alpinejs'
2+
import intersect from '@alpinejs/intersect'
23

34
import internationalNumber from './plugins/internationalNumber'
45
import './elements'
@@ -10,5 +11,6 @@ import './scrollspy'
1011
window.Alpine = Alpine;
1112

1213
Alpine.data('internationalNumber', internationalNumber)
14+
Alpine.plugin(intersect)
1315

1416
Alpine.start();
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
@props(['discussion'])
2+
3+
<div class="py-6 border-b border-skin-base">
4+
<div class="mt-2 lg:flex lg:items-center lg:justify-between">
5+
<div class="flex-1">
6+
<h2 class="text-xl font-semibold font-sans text-skin-inverted leading-7">
7+
<a href="{{ route('discussions.show', $discussion) }}" class="hover:text-skin-primary">{{ $discussion->title }}</a>
8+
</h2>
9+
</div>
10+
<div class="mt-2 lg:mt-0 flex-shrink-0 self-start">
11+
@if (count($tags = $discussion->tags))
12+
<div class="flex flex-wrap gap-2 lg:gap-x-3">
13+
@foreach ($tags as $tag)
14+
<x-tag :tag="$tag" />
15+
@endforeach
16+
</div>
17+
@endif
18+
</div>
19+
</div>
20+
<p class="mt-1 text-sm font-normal text-skin-base leading-5">
21+
{!! $discussion->excerpt(175) !!}
22+
</p>
23+
<div class="mt-3 flex justify-between">
24+
<div class="flex items-center text-sm font-sans text-skin-muted">
25+
<a class="flex-shrink-0" href="{{ route('profile', $discussion->author->username) }}">
26+
<img class="h-6 w-6 rounded-full" src="{{ $discussion->author->profile_photo_url }}" alt="{{ $discussion->author->name }}">
27+
</a>
28+
<span class="ml-2 pr-1">Posté par</span>
29+
<div class="flex items-center space-x-1">
30+
<a href="{{ route('profile', $discussion->author->username) }}" class="text-skin-inverted hover:underline">{{ $discussion->author->name }}</a>
31+
<span aria-hidden="true">&middot;</span>
32+
<time-ago time="{{ $discussion->created_at->getTimestamp() }}"/>
33+
</div>
34+
</div>
35+
<div class="flex items-center text-sm space-x-4">
36+
<p class="inline-flex space-x-2 text-skin-base">
37+
<x-heroicon-o-chat-alt-2 class="h-5 w-5" />
38+
<span class="font-normal text-skin-inverted-muted">{{ $discussion->count_all_replies_with_child }}</span>
39+
<span class="sr-only">réponses</span>
40+
</p>
41+
<a href="{{ route('discussions.edit', $discussion->slug()) }}" class="inline-flex items-center font-normal text-skin-inverted-muted hover:text-skin-base hover:underline">
42+
<x-heroicon-o-pencil class="h-4 w-4 mr-1.5" />
43+
Éditer
44+
</a>
45+
</div>
46+
</div>
47+
</div>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
@props(['thread'])
2+
3+
<div class="py-6 border-b border-skin-base">
4+
<article aria-labelledby="question-title-{{ $thread->id }}">
5+
<div>
6+
<div class="lg:flex lg:space-x-3">
7+
<div class="flex-1 flex items-center space-x-3">
8+
<div class="flex-shrink-0">
9+
<img class="h-8 w-8 rounded-full" src="{{ $thread->author->profile_photo_url }}" alt="">
10+
</div>
11+
<div class="min-w-0 flex-1 flex items-center space-x-1 font-sans">
12+
<p class="text-sm font-medium text-skin-inverted">
13+
<a href="{{ route('profile', $thread->author->username) }}" class="block group">
14+
<span class="group-hover:underline">{{ $thread->author->name }}</span>
15+
<span class="text-skin-muted">{{ '@'. $thread->author->username }}</span>
16+
</a>
17+
</p>
18+
<span aria-hidden="true">&middot;</span>
19+
<p class="text-sm text-skin-base">
20+
<time-ago time="{{ $thread->created_at->getTimestamp() }}"/>
21+
</p>
22+
</div>
23+
</div>
24+
<div class="mt-2 lg:mt-0 flex-shrink-0 self-center">
25+
@if (count($channels = $thread->channels->load('parent')))
26+
<div class="flex flex-wrap gap-2 mt-2 lg:mt-0 lg:gap-x-3">
27+
@foreach ($channels as $channel)
28+
<a href="{{ route('forum.channels', $channel) }}" class="flex gap-2">
29+
<x-forum.channel :channel="$channel" />
30+
</a>
31+
@endforeach
32+
</div>
33+
@endif
34+
</div>
35+
</div>
36+
<h2 id="question-title-{{ $thread->id }}" class="mt-4 text-base font-medium text-skin-inverted font-sans">
37+
<a href="{{ route('forum.show', $thread) }}" class="hover:underline">{{ $thread->subject() }}</a>
38+
</h2>
39+
</div>
40+
<div class="mt-2 text-sm text-skin-inverted-muted font-normal">
41+
<a href="{{ route('forum.show', $thread) }}">{!! $thread->excerpt() !!}</a>
42+
</div>
43+
<div class="mt-6 flex justify-between space-x-8">
44+
<div class="flex items-center space-x-4">
45+
<div class="flex items-center text-sm text-skin-inverted-muted font-normal">
46+
<span class="text-base mr-2">👏</span>
47+
{{ count($thread->reactions) }}
48+
</div>
49+
<p class="inline-flex text-sm space-x-2 text-skin-base">
50+
<x-heroicon-o-chat-alt class="h-5 w-5" />
51+
<span class="font-normal text-skin-inverted-muted">{{ count($thread->replies) }}</span>
52+
<span class="sr-only">réponses</span>
53+
</p>
54+
</div>
55+
<div class="flex text-sm">
56+
<p class="inline-flex items-center space-x-3 text-sm">
57+
@if ($thread->isSolved())
58+
<a href="{{ route('forum.show', $thread->slug) }}#{{ $thread->solution_reply_id }}" class="flex items-center gap-x-2 font-medium text-green-500">
59+
<x-heroicon-s-badge-check class="w-5 h-5" />
60+
<span class="hover:underline">Résolu</span>
61+
</a>
62+
@endif
63+
</p>
64+
</div>
65+
</div>
66+
</article>
67+
</div>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<div class="p-4 w-full mx-auto bg-skin-card border border-skin-base shadow-sm rounded-md">
2+
<div class="animate-pulse flex space-x-4">
3+
<div class="flex-1 space-y-4 py-1">
4+
<div class="h-4 bg-skin-card-muted rounded w-3/4"></div>
5+
<div class="space-y-2">
6+
<div class="h-4 bg-skin-card-muted rounded"></div>
7+
<div class="h-4 bg-skin-card-muted rounded w-5/6"></div>
8+
<div class="h-4 bg-skin-card-muted rounded w-5/6"></div>
9+
</div>
10+
</div>
11+
</div>
12+
</div>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
@props(['section'])
2+
3+
<nav class="flex mb-3" aria-label="Breadcrumb">
4+
<ol role="list" class="flex items-center space-x-4">
5+
<li>
6+
<div>
7+
<a href="{{ route('dashboard') }}" class="text-skin-muted hover:text-skin-base">
8+
<x-heroicon-s-home class="flex-shrink-0 h-5 w-5" />
9+
<span class="sr-only">Accueil</span>
10+
</a>
11+
</div>
12+
</li>
13+
14+
<li>
15+
<div class="flex items-center">
16+
<svg class="flex-shrink-0 h-5 w-5 text-gray-300" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
17+
<path d="M5.555 17.776l8-16 .894.448-8 16-.894-.448z" />
18+
</svg>
19+
<span href="#" class="ml-4 text-sm font-medium text-skin-base" aria-current="page">{{ $section }}</span>
20+
</div>
21+
</li>
22+
</ol>
23+
</nav>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@props(['title', 'button', 'url'])
2+
3+
<div class="md:flex md:items-center md:justify-between">
4+
<div class="flex-1 min-w-0">
5+
<h2 class="text-lg font-bold leading-7 text-skin-inverted sm:text-xl sm:truncate font-sans">
6+
{{ $title }}
7+
</h2>
8+
</div>
9+
<div class="mt-4 flex md:mt-0 md:ml-4">
10+
<x-button :link="$url" class="ml-3">
11+
{{ $button }}
12+
</x-button>
13+
</div>
14+
</div>

0 commit comments

Comments
 (0)