Skip to content

Commit 6d9f84d

Browse files
committed
feat: LAR-175 upgrade the approbation system
1 parent 621cee1 commit 6d9f84d

File tree

12 files changed

+190
-17
lines changed

12 files changed

+190
-17
lines changed
Lines changed: 31 additions & 0 deletions
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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 21 additions & 6 deletions
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\RichEditor;
14+
use Filament\Notifications\Notification;
1215
use Filament\Resources\Resource;
1316
use Filament\Support\Enums\MaxWidth;
1417
use Filament\Tables;
@@ -123,16 +126,28 @@ 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+
RichEditor::make('reason')
131+
->label(__('Raison du refus'))
132+
->required(),
133+
])
134+
->modalHeading('Décliner l\'article')
135+
->modalDescription('Veuillez fournir une raison détaillée pour le refus de cet article. L\'auteur recevra cette explication.')
136+
->successNotificationTitle('Article décliné avec succès')
128137
->requiresConfirmation()
129138
->modalIcon('heroicon-s-x-mark')
130-
->action(function ($record): void {
139+
->action(function (array $data, Article $record): void {
131140
Gate::authorize('decline', $record);
132141

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

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

Lines changed: 10 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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',
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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'];
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+
}
Lines changed: 17 additions & 0 deletions
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->longText('reason')->nullable()->after('canonical_url');
15+
});
16+
}
17+
};

lang/en/emails/article.php

Lines changed: 20 additions & 0 deletions
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

Lines changed: 20 additions & 0 deletions
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+
];
Lines changed: 28 additions & 0 deletions
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

Lines changed: 7 additions & 11 deletions
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)