Skip to content

Commit 6dc96f1

Browse files
authored
feat: LAR-175 upgrade the approbation system (#340)
2 parents 700bdf1 + f313a47 commit 6dc96f1

File tree

12 files changed

+201
-16
lines changed

12 files changed

+201
-16
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Actions\Article;
6+
7+
use App\Models\Article;
8+
use App\Notifications\ArticleDeclinedNotification;
9+
use Carbon\Carbon;
10+
use Illuminate\Support\Facades\DB;
11+
12+
final class DeclineArticleAction
13+
{
14+
public function execute(string $reason, Article $article): Article
15+
{
16+
return DB::transaction(function () use ($reason, $article) {
17+
18+
$article->update([
19+
'declined_at' => Carbon::now(),
20+
'reason' => $reason,
21+
'submitted_at' => null,
22+
]);
23+
24+
$article->user->notify(new ArticleDeclinedNotification($article));
25+
26+
$article->refresh();
27+
28+
return $article;
29+
});
30+
}
31+
}

app/Actions/Article/UpdateArticleAction.php

+4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ public function execute(ArticleData $articleData, Article $article): Article
3131
);
3232
}
3333

34+
if ($articleData->declined_at) {
35+
$articleData->declined_at = null;
36+
}
37+
3438
$article->update($articleData->toArray());
3539

3640
$article->refresh();

app/Data/ArticleData.php

+1
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ public function __construct(
1717
public ?string $canonical_url = null,
1818
public ?Carbon $published_at = null,
1919
public ?Carbon $submitted_at = null,
20+
public ?Carbon $declined_at = null,
2021
) {}
2122
}

app/Filament/Resources/ArticleResource.php

+20-5
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
namespace App\Filament\Resources;
66

77
use App\Actions\Article\ApprovedArticleAction;
8+
use App\Actions\Article\DeclineArticleAction;
89
use App\Filament\Resources\ArticleResource\Pages;
910
use App\Models\Article;
1011
use Awcodes\FilamentBadgeableColumn\Components\Badge;
1112
use Awcodes\FilamentBadgeableColumn\Components\BadgeableColumn;
13+
use Filament\Forms\Components\Textarea;
14+
use Filament\Notifications\Notification;
1215
use Filament\Resources\Resource;
1316
use Filament\Support\Enums\MaxWidth;
1417
use Filament\Tables;
@@ -123,15 +126,27 @@ public static function table(Table $table): Table
123126
->label('Décliner')
124127
->icon('heroicon-s-x-mark')
125128
->color('warning')
126-
->modalHeading(__('Voulez vous décliner cet article'))
127-
->successNotificationTitle(__('Opération effectuée avec succès'))
129+
->form([
130+
Textarea::make('reason')
131+
->label(__('Raison du refus'))
132+
->maxLength(255)
133+
->required(),
134+
])
135+
->modalHeading('Décliner l\'article')
136+
->modalDescription('Veuillez fournir une raison détaillée pour le refus de cet article. L\'auteur recevra cette explication.')
137+
->successNotificationTitle('Article décliné avec succès')
128138
->requiresConfirmation()
129139
->modalIcon('heroicon-s-x-mark')
130-
->action(function ($record): void {
140+
->action(function (array $data, Article $record): void {
131141
Gate::authorize('decline', $record);
132142

133-
$record->declined_at = now();
134-
$record->save();
143+
app(DeclineArticleAction::class)->execute($data['reason'], $record);
144+
145+
Notification::make()
146+
->title('Article décliné')
147+
->body('L\'auteur a été notifié de la raison du refus.')
148+
->success()
149+
->send();
135150
}),
136151
Tables\Actions\Action::make('show')
137152
->icon('untitledui-eye')

app/Filament/Resources/ArticleResource/Pages/ListArticles.php

+10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use App\Filament\Resources\ArticleResource;
88
use App\Models\Article;
99
use Closure;
10+
use Filament\Resources\Components\Tab;
1011
use Filament\Resources\Pages\ListRecords;
1112

1213
final class ListArticles extends ListRecords
@@ -17,4 +18,13 @@ public function isTableRecordSelectable(): Closure
1718
{
1819
return fn (Article $record): bool => $record->isNotPublished();
1920
}
21+
22+
public function getTabs(): array
23+
{
24+
return [
25+
'En attente' => Tab::make()->query(fn ($query) => $query->awaitingApproval()),
26+
'Apprové' => Tab::make()->query(fn ($query) => $query->published()),
27+
'Décliné' => Tab::make()->query(fn ($query) => $query->declined()),
28+
];
29+
}
2030
}

app/Models/Article.php

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
* @property bool $is_pinned
3333
* @property int $is_sponsored
3434
* @property string | null $canonical_url
35+
* @property string | null $reason
3536
* @property int | null $tweet_id
3637
* @property int $user_id
3738
* @property string | null $locale
@@ -63,6 +64,7 @@ final class Article extends Model implements HasMedia, ReactableInterface, Sitem
6364
'body',
6465
'slug',
6566
'canonical_url',
67+
'reason',
6668
'show_toc',
6769
'is_pinned',
6870
'user_id',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Notifications;
6+
7+
use App\Models\Article;
8+
use Illuminate\Bus\Queueable;
9+
use Illuminate\Notifications\Messages\MailMessage;
10+
use Illuminate\Notifications\Notification;
11+
12+
final class ArticleDeclinedNotification extends Notification
13+
{
14+
use Queueable;
15+
16+
public function __construct(public Article $article) {}
17+
18+
public function via(mixed $notifiable): array
19+
{
20+
return ['mail', 'database'];
21+
}
22+
23+
public function toMail(mixed $notifiable): MailMessage
24+
{
25+
return (new MailMessage)
26+
->subject(__('emails/article.article_declined.subject'))
27+
->markdown('emails.article_declined', ['article' => $this->article]);
28+
}
29+
30+
/**
31+
* @return array<string, mixed>
32+
*/
33+
public function toArray(): array
34+
{
35+
return [
36+
'article' => $this->article,
37+
'owner' => $this->article->user->name,
38+
'email' => $this->article->user->email,
39+
];
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Illuminate\Database\Migrations\Migration;
6+
use Illuminate\Database\Schema\Blueprint;
7+
use Illuminate\Support\Facades\Schema;
8+
9+
return new class extends Migration
10+
{
11+
public function up(): void
12+
{
13+
Schema::table('articles', static function (Blueprint $table): void {
14+
$table->string('reason')->nullable()->after('canonical_url');
15+
});
16+
}
17+
};

lang/en/emails/article.php

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
return [
6+
7+
'article_declined' => [
8+
'subject' => 'Your article has been declined',
9+
'head' => 'Your article :title has been declined for the following reason:',
10+
'recommandation_body' => 'Don\'t be discouraged! You can:',
11+
'recommandation_1' => '1. Review your article and the reason for the decline',
12+
'recommandation_2' => '2. Make the necessary changes',
13+
'recommandation_3' => '3. Resubmit your article once the corrections are made',
14+
'help' => 'Our team is here to help you improve your content.',
15+
'closing' => 'Best regards,',
16+
'team' => 'The Laravel Cameroon Team',
17+
'button_update_article' => 'Edit my article',
18+
],
19+
20+
];

lang/fr/emails/article.php

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
return [
6+
7+
'article_declined' => [
8+
'subject' => 'Votre article a été décliné',
9+
'head' => 'Votre article :title a été décliné pour la raison suivante :',
10+
'recommandation_body' => 'Ne vous découragez pas ! Vous pouvez :',
11+
'recommandation_1' => '1. Consulter votre article et la raison du refus',
12+
'recommandation_2' => '2. Apporter les modifications nécessaires',
13+
'recommandation_3' => '3. Re-soumettre votre article une fois les corrections effectuées',
14+
'help' => 'Notre équipe est là pour vous aider à améliorer votre contenu.',
15+
'closing' => 'Cordialement,',
16+
'team' => 'L\'équipe Laravel Cameroun',
17+
'button_update_article' => 'Modifier mon article',
18+
],
19+
20+
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<x-mail::message>
2+
3+
<x-mail::panel>
4+
5+
{{ __('emails/article.article_declined.head', ['title' => $article->title]) }}
6+
7+
{{ $article->reason }}
8+
9+
{{ __('emails/article.article_declined.recommandation_body') }}
10+
11+
{{ __('emails/article.article_declined.recommandation_1') }}
12+
{{ __('emails/article.article_declined.recommandation_2') }}
13+
{{ __('emails/article.article_declined.recommandation_3') }}
14+
15+
<x-mail::button :url="route('articles.show', $article)">
16+
{{ __('emails/article.article_declined.button_update_article') }}
17+
</x-mail::button>
18+
19+
{{ __('emails/article.article_declined.help') }}
20+
21+
</x-mail::panel>
22+
23+
<p>
24+
{{ __('emails/article.article_declined.closing') }} <br>
25+
{{ __('emails/article.article_declined.team') }}
26+
</p>
27+
28+
</x-mail::message>

tests/Feature/Filament/ArticleResourceTest.php

+7-11
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,8 @@
3535

3636
$article->refresh();
3737

38-
expect($article->approved_at)
39-
->not()
40-
->toBe(null)
41-
->and($article->declined_at)
42-
->toBe(null);
38+
expect($article->approved_at)->toBeInstanceOf(\Carbon\Carbon::class)
39+
->and($article->declined_at)->toBeNull();
4340

4441
Livewire::test(ArticleResource\Pages\ListArticles::class)
4542
->assertTableActionHidden('approved', $article)
@@ -50,15 +47,14 @@
5047
$article = $this->articles->first();
5148

5249
Livewire::test(ArticleResource\Pages\ListArticles::class)
53-
->callTableAction('declined', $article);
50+
->callTableAction('declined', $article, data: [
51+
'reason' => 'Ce contenu ne respecte pas nos règles éditoriales.',
52+
]);
5453

5554
$article->refresh();
5655

57-
expect($article->declined_at)
58-
->not
59-
->toBe(null)
60-
->and($article->approved_at)
61-
->toBe(null);
56+
expect($article->declined_at)->toBeInstanceOf(\Carbon\Carbon::class)
57+
->and($article->approved_at)->toBeNull();
6258

6359
Livewire::test(ArticleResource\Pages\ListArticles::class)
6460
->assertTableActionHidden('approved', $article)

0 commit comments

Comments
 (0)