diff --git a/app/Enums/PostStatus.php b/app/Enums/PostStatus.php new file mode 100644 index 0000000..26b3f04 --- /dev/null +++ b/app/Enums/PostStatus.php @@ -0,0 +1,35 @@ + __('posts.status.Draft'), + self::Published => __('posts.status.Published'), + }; + } + + public function color(): string + { + return match ($this) { + self::Draft => 'bg-yellow-500', + self::Published => 'bg-green-500', + }; + } + + public function badgeColor(): string + { + return match ($this) { + self::Draft => 'yellow', + self::Published => 'green', + }; + } +} diff --git a/app/Http/Controllers/PostController.php b/app/Http/Controllers/PostController.php index 2f6fa48..0ec9d8d 100644 --- a/app/Http/Controllers/PostController.php +++ b/app/Http/Controllers/PostController.php @@ -13,6 +13,7 @@ use App\Models\Post; use App\Support\QueryResolver; use App\Support\Settings; +use App\Enums\PostStatus; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\View\View; @@ -44,6 +45,7 @@ public function create(): View return view('posts.create', [ 'categories' => Category::query()->pluck('title', 'id'), 'tags' => Settings::getTags(), + 'statuses' => PostStatus::cases(), ]); } @@ -77,6 +79,7 @@ public function edit(Post $post): View 'post' => $post, 'categories' => Category::query()->pluck('title', 'id'), 'tags' => Settings::getTags(), + 'statuses' => PostStatus::cases(), ]); } diff --git a/app/Http/Requests/StorePostRequest.php b/app/Http/Requests/StorePostRequest.php index 4ee9ed1..8d90e61 100644 --- a/app/Http/Requests/StorePostRequest.php +++ b/app/Http/Requests/StorePostRequest.php @@ -4,9 +4,11 @@ namespace App\Http\Requests; +use App\Enums\PostStatus; use App\Traits\HasFileFromUrl; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Validation\Rules\Enum; use Override; final class StorePostRequest extends FormRequest @@ -35,6 +37,7 @@ public function rules(): array 'image' => ['required', 'image'], 'content' => ['required'], 'published_at' => ['nullable', 'date'], + 'status' => ['required', 'string', new Enum(PostStatus::class)], 'category_id' => [ 'required', 'exists:categories,id', diff --git a/app/Models/Post.php b/app/Models/Post.php index 3a4650d..6087cea 100644 --- a/app/Models/Post.php +++ b/app/Models/Post.php @@ -18,6 +18,7 @@ use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Storage; use Mews\Purifier\Casts\CleanHtmlInput; +use App\Enums\PostStatus; use Override; /** @@ -30,6 +31,7 @@ * @property UploadedFile|string|null $image * @property string $content * @property Carbon|null $published_at + * @property PostStatus $status * @property int $category_id * @property array|null $tags * @property FeaturedStatus $is_featured @@ -48,6 +50,7 @@ 'image', 'content', 'published_at', + 'status', 'category_id', 'tags', 'is_featured', @@ -125,6 +128,7 @@ protected function casts(): array 'published_at' => 'datetime', 'is_featured' => FeaturedStatus::class, 'content' => CleanHtmlInput::class, + 'status' => PostStatus::class, ]; } } diff --git a/database/factories/PostFactory.php b/database/factories/PostFactory.php index 9c4520b..1da4a35 100644 --- a/database/factories/PostFactory.php +++ b/database/factories/PostFactory.php @@ -30,6 +30,7 @@ public function definition(): array 'image' => $this->fakeImageUrl(), 'content' => $this->faker->randomHtml(), 'published_at' => $this->faker->dateTimeBetween('-1 month', '+3 months'), + 'status' => $this->faker->randomElement(array_column(PostStatus::cases(), 'value')), 'category_id' => CategoryFactory::new(), 'tags' => $this->faker->randomElements(['Eloquent', 'Blade', 'Migrations', 'Seeding', 'Routing', 'Controllers', 'Middleware', 'Requests', 'Responses', 'Views', 'Forms', 'Validation', 'Mail', 'Notifications'], $this->faker->numberBetween(1, 3)), 'is_featured' => $this->faker->randomElement(array_column(FeaturedStatus::cases(), 'value')), diff --git a/database/migrations/2026_05_06_171934_add_status_to_posts_table.php b/database/migrations/2026_05_06_171934_add_status_to_posts_table.php new file mode 100644 index 0000000..e3f48db --- /dev/null +++ b/database/migrations/2026_05_06_171934_add_status_to_posts_table.php @@ -0,0 +1,28 @@ +string('status')->default('draft')->after('published_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // did not prefer to add down method, I just add another migration if i want to rollback + } +}; diff --git a/lang/ar/posts.php b/lang/ar/posts.php index f3d5eb0..5818ae8 100644 --- a/lang/ar/posts.php +++ b/lang/ar/posts.php @@ -10,6 +10,8 @@ 'Image' => 'الصورة', 'Category' => 'التصنيف', 'Select a Category' => 'اختر التصنيف', + 'Status' => 'الحالة', + 'Select a Status' => 'اختر الحالة', 'Description' => 'الوصف', 'Tags' => 'الوسوم', 'Featured' => 'مميز', @@ -29,6 +31,10 @@ 'Update Post' => 'تعديل المقال', 'Cancel' => 'إلغاء', ], + 'status' => [ + 'Draft' => 'مسودة', + 'Published' => 'منشور', + ], 'show' => [ 'Not Featured' => 'غير مميز', 'Featured' => 'مميز', diff --git a/lang/en/posts.php b/lang/en/posts.php index b6f0f8a..47fee70 100644 --- a/lang/en/posts.php +++ b/lang/en/posts.php @@ -10,6 +10,8 @@ 'Image' => 'Image', 'Category' => 'Category', 'Select a Category' => 'Select a Category', + 'Status' => 'Status', + 'Select a Status' => 'Select a Status', 'Description' => 'Description', 'Tags' => 'Tags', 'Featured' => 'Featured', @@ -29,6 +31,10 @@ 'Update Post' => 'Update Post', 'Cancel' => 'Cancel', ], + 'status' => [ + 'Draft' => 'Draft', + 'Published' => 'Published', + ], 'show' => [ 'Not Featured' => 'Not Featured', 'Featured' => 'Featured', diff --git a/lang/fr/posts.php b/lang/fr/posts.php index 1af48fc..9b2dd22 100644 --- a/lang/fr/posts.php +++ b/lang/fr/posts.php @@ -10,6 +10,8 @@ 'Image' => 'Image', 'Category' => 'Catégorie', 'Select a Category' => 'Sélectionnez une catégorie', + 'Status' => 'Statut', + 'Select a Status' => 'Sélectionnez un statut', 'Description' => 'Description', 'Tags' => 'Tags', 'Featured' => 'En vedette', @@ -29,6 +31,10 @@ 'Update Post' => 'Mettre à jour l\'article', 'Cancel' => 'Annuler', ], + 'status' => [ + 'Draft' => 'Brouillon', + 'Published' => 'Publié', + ], 'show' => [ 'Not Featured' => 'Pas en vedette', 'Featured' => 'En vedette', diff --git a/lang/gu/posts.php b/lang/gu/posts.php index f79e755..288a617 100644 --- a/lang/gu/posts.php +++ b/lang/gu/posts.php @@ -11,6 +11,8 @@ 'Image' => 'છબી', 'Category' => 'વર્ગ', 'Select a Category' => 'એક વર્ગ પસંદ કરો', + 'Status' => 'સ્થિતિ', + 'Select a Status' => 'એક સ્થિતિ પસંદ કરો', 'Description' => 'વર્ણન', 'Tags' => 'ટેગ્સ', 'Featured' => 'ફિચર્ડ', @@ -30,6 +32,10 @@ 'Update Post' => 'પોસ્ટ અપડેટ કરો', 'Cancel' => 'રદ કરો', ], + 'status' => [ + 'Draft' => 'ડ્રાફ્ટ', + 'Published' => 'પ્રકાશિત', + ], 'show' => [ 'Not Featured' => 'ફિચર્ડ નથી', 'Featured' => 'ફિચર્ડ', diff --git a/lang/hi/posts.php b/lang/hi/posts.php index c1f3755..45deb3d 100644 --- a/lang/hi/posts.php +++ b/lang/hi/posts.php @@ -29,6 +29,10 @@ 'Update Post' => 'पोस्ट अपडेट करें', 'Cancel' => 'रद करना', ], + 'status' => [ + 'Draft' => 'मसौदा', + 'Published' => 'प्रकाशित', + ], 'show' => [ 'Not Featured' => 'विशेष रुप से प्रदर्शित नहीं', 'Featured' => 'विशेष रुप से प्रदर्शित', @@ -51,4 +55,6 @@ 'Post featured successfully' => 'पोस्ट सफलतापूर्वक विशेष रुप से प्रदर्शित की गई।', 'Post unfeatured successfully' => 'पोस्ट सफलतापूर्वक विशेष रुप से प्रदर्शित नहीं की गई।', ], +]; + ], ]; diff --git a/resources/views/posts/_form.blade.php b/resources/views/posts/_form.blade.php index eed2bc6..1cb846d 100644 --- a/resources/views/posts/_form.blade.php +++ b/resources/views/posts/_form.blade.php @@ -61,6 +61,20 @@

Publishing

+ + {{ __('posts.form.Status') }} + + + @foreach ($statuses as $status) + + @endforeach + + @error('status') + {{ $message }} + @enderror + {{ __('posts.form.Category') }} diff --git a/tests/Feature/Http/Controllers/PostControllerTest.php b/tests/Feature/Http/Controllers/PostControllerTest.php index ef4e1d5..526d82d 100644 --- a/tests/Feature/Http/Controllers/PostControllerTest.php +++ b/tests/Feature/Http/Controllers/PostControllerTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature; +use App\Enums\PostStatus; use App\Models\Category; use App\Models\Post; use Illuminate\Http\UploadedFile; @@ -111,6 +112,7 @@ ->assertViewHasAll([ 'categories', 'tags', + 'statuses', ]); }); @@ -125,6 +127,7 @@ 'category_id' => $category->id, 'description' => 'this is the description', 'content' => 'this is the content', + 'status' => PostStatus::Draft->value, 'tags' => ['Eloquent'], ]) ->assertRedirect(route('posts.index', ['published' => true])) @@ -144,6 +147,7 @@ 'category_id', 'description', 'content', + 'status', ]); }); @@ -168,6 +172,7 @@ 'post', 'categories', 'tags', + 'statuses', ]); }); @@ -186,6 +191,7 @@ 'category_id' => $CategoryId, 'description' => 'this is the description', 'content' => 'this is the content', + 'status' => PostStatus::Published->value, 'tags' => ['Eloquent'], ]) ->assertRedirect(route('posts.index', ['sortBy' => 'title', 'direction' => 'asc'])) diff --git a/tests/Unit/Enums/PostStatusTest.php b/tests/Unit/Enums/PostStatusTest.php new file mode 100644 index 0000000..0d36b94 --- /dev/null +++ b/tests/Unit/Enums/PostStatusTest.php @@ -0,0 +1,61 @@ +label(); + + expect($label)->toBe(__('posts.status.Draft')); +}); + +it('returns the correct label for Published', function (): void { + $status = PostStatus::Published; + $label = $status->label(); + + expect($label)->toBe(__('posts.status.Published')); +}); + +it('returns the correct color for Draft', function (): void { + $status = PostStatus::Draft; + $color = $status->color(); + + expect($color)->toBe('bg-yellow-500'); +}); + +it('returns the correct color for Published', function (): void { + $status = PostStatus::Published; + $color = $status->color(); + + expect($color)->toBe('bg-green-500'); +}); + +it('returns the correct badge color for Draft', function (): void { + $status = PostStatus::Draft; + $badgeColor = $status->badgeColor(); + + expect($badgeColor)->toBe('yellow'); +}); + +it('returns the correct badge color for Published', function (): void { + $status = PostStatus::Published; + $badgeColor = $status->badgeColor(); + + expect($badgeColor)->toBe('green'); +}); + +it('returns the correct badge color', function (string $value, string $expected): void { + expect(PostStatus::tryFrom($value)?->badgeColor())->toBe($expected); +}) + ->with([ + PostStatus::Draft->name => [ + 'value' => PostStatus::Draft->value, + 'expected' => 'yellow', + ], + PostStatus::Published->name => [ + 'value' => PostStatus::Published->value, + 'expected' => 'green', + ], + ]);