From ecd16e5d41409a1de330068b6d45758ce1860326 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Mon, 22 Dec 2025 22:54:36 -0600 Subject: [PATCH] Working News Section --- .ai/mcp/mcp.json | 20 ++++++ app/Enums/StoryStatusEnum.php | 31 ++++++++++ .../Controllers/Admin/NewsStoryController.php | 36 ++++++----- app/Http/Controllers/WelcomeController.php | 6 +- app/Http/Requests/NewsStoryRequest.php | 49 +++++++++++++++ app/Models/NewsStory.php | 43 ++++++++++++- database/factories/NewsStoryFactory.php | 25 ++++++++ resources/views/admin/news/create.blade.php | 7 +++ resources/views/admin/news/edit.blade.php | 62 +++++++++++++++++++ resources/views/admin/news/index.blade.php | 39 +++++++++++- resources/views/components/alert.blade.php | 45 +++++++------- .../views/components/badge-pill.blade.php | 18 ++++++ .../views/components/modal-danger.blade.php | 49 +++++++++++++++ resources/views/welcome.blade.php | 23 ++++++- routes/web.php | 2 +- 15 files changed, 410 insertions(+), 45 deletions(-) create mode 100644 .ai/mcp/mcp.json create mode 100644 app/Enums/StoryStatusEnum.php create mode 100644 app/Http/Requests/NewsStoryRequest.php create mode 100644 database/factories/NewsStoryFactory.php create mode 100644 resources/views/admin/news/edit.blade.php create mode 100644 resources/views/components/badge-pill.blade.php create mode 100644 resources/views/components/modal-danger.blade.php diff --git a/.ai/mcp/mcp.json b/.ai/mcp/mcp.json new file mode 100644 index 0000000..592e550 --- /dev/null +++ b/.ai/mcp/mcp.json @@ -0,0 +1,20 @@ +{ + "mcpServers": { + "laravel-boost": { + "command": "/Users/mayoung/Library/Application Support/Herd/bin/php82", + "args": [ + "/Users/mayoung/Herd/meobda-website/artisan", + "boost:mcp" + ] + }, + "herd": { + "command": "/Users/mayoung/Library/Application Support/Herd/bin/php82", + "args": [ + "/Applications/Herd.app/Contents/Resources/herd-mcp.phar" + ], + "env": { + "SITE_PATH": "/Users/mayoung/Herd/meobda-website" + } + } + } +} \ No newline at end of file diff --git a/app/Enums/StoryStatusEnum.php b/app/Enums/StoryStatusEnum.php new file mode 100644 index 0000000..41bffc9 --- /dev/null +++ b/app/Enums/StoryStatusEnum.php @@ -0,0 +1,31 @@ + 'Draft', + self::SCHEDULED => 'Scheduled', + self::PUBLISHED => 'Published', + self::EXPIRED => 'Expired', + }; + } + + public function color(): string + { + return match ($this) { + self::DRAFT => 'yellow', + self::SCHEDULED => 'blue', + self::PUBLISHED => 'green', + self::EXPIRED => 'red', + }; + } +} diff --git a/app/Http/Controllers/Admin/NewsStoryController.php b/app/Http/Controllers/Admin/NewsStoryController.php index 6dc3ca0..ceedf1d 100644 --- a/app/Http/Controllers/Admin/NewsStoryController.php +++ b/app/Http/Controllers/Admin/NewsStoryController.php @@ -3,8 +3,8 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; +use App\Http\Requests\NewsStoryRequest; use App\Models\NewsStory; -use Illuminate\Http\Request; class NewsStoryController extends Controller { @@ -14,6 +14,7 @@ class NewsStoryController extends Controller public function index() { $stories = NewsStory::orderBy('id', 'desc')->paginate(15); + return view('admin.news.index', compact('stories')); } @@ -28,33 +29,35 @@ class NewsStoryController extends Controller /** * Store a newly created resource in storage. */ - public function store(Request $request) + public function store(NewsStoryRequest $request) { - // + NewsStory::create( + $request->validated() + ); + + return redirect()->route('admin.news.index')->with('success', 'Story Added Successfully'); } - /** - * Display the specified resource. - */ - public function show(string $id) - { - // - } /** * Show the form for editing the specified resource. */ - public function edit(string $id) + public function edit(int $newsStoryID) { - // + $newsStory = NewsStory::findOrFail($newsStoryID); + + return view('admin.news.edit', compact('newsStory')); } /** * Update the specified resource in storage. */ - public function update(Request $request, string $id) + public function update(NewsStoryRequest $request, string $id) { - // + $newsStory = NewsStory::findOrFail($id); + $newsStory->update($request->validated()); + + return redirect()->route('admin.news.index')->with('success', 'Story Updated Successfully'); } /** @@ -62,6 +65,9 @@ class NewsStoryController extends Controller */ public function destroy(string $id) { - // + $story = NewsStory::findOrFail($id); + $story->delete(); + + return redirect()->route('admin.news.index')->with('success', 'Story Deleted Successfully'); } } diff --git a/app/Http/Controllers/WelcomeController.php b/app/Http/Controllers/WelcomeController.php index 096c00e..49e30ee 100644 --- a/app/Http/Controllers/WelcomeController.php +++ b/app/Http/Controllers/WelcomeController.php @@ -2,6 +2,8 @@ namespace App\Http\Controllers; +use App\Models\NewsStory; + use function siteData; class WelcomeController extends Controller @@ -17,6 +19,7 @@ class WelcomeController extends Controller $beginnerClinicDates = siteData('beginnerClinicDates'); $beginnerClinicLocation = siteData('beginnerClinicLocation'); $officers = siteData('officers'); + $newsStories = NewsStory::published()->orderBy('updated_at', 'desc')->paginate(3); return view('welcome', compact( 'officers', @@ -27,6 +30,7 @@ class WelcomeController extends Controller 'concertClinicDates', 'concertClinicLocation', 'beginnerClinicDates', - 'beginnerClinicLocation')); + 'beginnerClinicLocation', + 'newsStories')); } } diff --git a/app/Http/Requests/NewsStoryRequest.php b/app/Http/Requests/NewsStoryRequest.php new file mode 100644 index 0000000..e8d259c --- /dev/null +++ b/app/Http/Requests/NewsStoryRequest.php @@ -0,0 +1,49 @@ +merge([ + 'active' => $this->boolean('active'), + 'scheduleStart' => $this->boolean('scheduleStart'), + 'scheduleEnd' => $this->boolean('scheduleEnd'), + 'start_publication_date' => $this->boolean('scheduleStart') + ? $this->input('start_publication_date') + : now()->format('Y-m-d'), + 'stop_publication_date' => $this->boolean('scheduleEnd') + ? $this->input('stop_publication_date') + : '2100-01-01', + ]); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return [ + 'headline' => ['required', 'string'], + 'body' => ['required', 'string'], + 'active' => ['required', 'boolean'], + 'scheduleStart' => ['sometimes', 'boolean'], + 'start_publication_date' => ['nullable', 'date'], + 'scheduleEnd' => ['sometimes', 'boolean'], + 'stop_publication_date' => ['nullable', 'date', 'after_or_equal:start_publication_date'], + ]; + } +} diff --git a/app/Models/NewsStory.php b/app/Models/NewsStory.php index b0a949d..b8dce9d 100644 --- a/app/Models/NewsStory.php +++ b/app/Models/NewsStory.php @@ -2,9 +2,50 @@ namespace App\Models; +use App\Enums\StoryStatusEnum; +use Illuminate\Database\Eloquent\Casts\Attribute; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class NewsStory extends Model { - // + use HasFactory; + + protected $fillable = [ + 'headline', + 'body', + 'active', + 'start_publication_date', + 'stop_publication_date', + ]; + + protected function casts(): array + { + return [ + 'active' => 'boolean', + 'start_publication_date' => 'date', + 'stop_publication_date' => 'date', + ]; + } + + protected function status(): Attribute + { + return Attribute::make( + get: fn () => match (true) { + ! $this->active => StoryStatusEnum::DRAFT, + $this->start_publication_date && now() < $this->start_publication_date => StoryStatusEnum::SCHEDULED, + $this->stop_publication_date && now() >= $this->stop_publication_date => StoryStatusEnum::EXPIRED, + default => StoryStatusEnum::PUBLISHED, + } + ); + } + + public function scopePublished($query) + { + return $query->where('active', true) + ->where(fn ($q) => $q->whereNull('start_publication_date') + ->orWhereDate('start_publication_date', '<=', now())) + ->where(fn ($q) => $q->whereNull('stop_publication_date') + ->orWhereDate('stop_publication_date', '>=', now())); + } } diff --git a/database/factories/NewsStoryFactory.php b/database/factories/NewsStoryFactory.php new file mode 100644 index 0000000..f3f1089 --- /dev/null +++ b/database/factories/NewsStoryFactory.php @@ -0,0 +1,25 @@ + $this->faker->sentence(), + 'body' => $this->faker->realText(), + 'start_publication_date' => Carbon::now(), + 'stop_publication_date' => Carbon::now()->addDays(7), + 'active' => '1', + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]; + } +} diff --git a/resources/views/admin/news/create.blade.php b/resources/views/admin/news/create.blade.php index f8d6f72..3582da3 100644 --- a/resources/views/admin/news/create.blade.php +++ b/resources/views/admin/news/create.blade.php @@ -1,8 +1,15 @@ Create News Story + + @if($errors->any()) + @foreach($errors->all() as $error) + {{ $error }} + @endforeach + @endif +
diff --git a/resources/views/admin/news/edit.blade.php b/resources/views/admin/news/edit.blade.php new file mode 100644 index 0000000..5bc899a --- /dev/null +++ b/resources/views/admin/news/edit.blade.php @@ -0,0 +1,62 @@ + + + Create News Story + + + @if($errors->any()) + @foreach($errors->all() as $error) + {{ $error }} + @endforeach + @endif + +
+ +
+
+ + {{ $newsStory->body }} + +
+
+ + Status + @if($newsStory->active) + Active + Draft + @else + Active + Draft + @endif + +
+ +
+ + +
+
+ + +
+
+ Save Story +
+
+
+
+
diff --git a/resources/views/admin/news/index.blade.php b/resources/views/admin/news/index.blade.php index 506dab9..9b58065 100644 --- a/resources/views/admin/news/index.blade.php +++ b/resources/views/admin/news/index.blade.php @@ -1,10 +1,47 @@ - + News Stories
New Story
+
+ + + + Headline + Status + Submission Date + Start Publication + Stop Publication + + @foreach($stories as $story) + + + + + + + Confirm Story Deletion + Delete + + Do you really want to delete the story with the headline
{{ $story->headline }}? +
+
+ {{ $story->headline }} + + + {{ $story->status->label() }} + + + {{ $story->created_at->format('m/d/Y') }} + {{ $story->start_publication_date->format('m/d/Y') }} + {{ $story->stop_publication_date?->format('m/d/Y') }} + + + @endforeach +
+
diff --git a/resources/views/components/alert.blade.php b/resources/views/components/alert.blade.php index e6ab0b5..8acb1dd 100644 --- a/resources/views/components/alert.blade.php +++ b/resources/views/components/alert.blade.php @@ -1,38 +1,37 @@ @props(['color' => 'blue']) -
-
$color === 'yellow', 'border-red-400 bg-red-50 dark:border-red-500 dark:bg-red-500/15' => $color === 'red', 'border-green-400 bg-green-50 dark:border-green-500 dark:bg-green-500/10' => $color === 'green', 'border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-500/10' => $color === 'blue', ])> -
-
-
+
+ -
-
-

+ + +

+
+

$color === 'yellow', 'text-blue-700 dar:text-blue-300' => $color === 'blue', 'text-red-700 dar:text-red-300' => $color === 'red', 'text-green-700 dar:text-green-300' => $color === 'green', ])> - You have no credits left. -

-
+ {{ $slot }} +

+
diff --git a/resources/views/components/badge-pill.blade.php b/resources/views/components/badge-pill.blade.php new file mode 100644 index 0000000..3b4fed8 --- /dev/null +++ b/resources/views/components/badge-pill.blade.php @@ -0,0 +1,18 @@ +@props(['color' => 'blue']) + + $color === 'red', + 'bg-gray-50 text-gray-600 inset-ring inset-ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:inset-ring-gray-400/20' => $color === 'gray', + 'bg-yellow-50 text-yellow-800 inset-ring inset-ring-yellow-600/20 dark:bg-yellow-400/10 dark:text-yellow-500 dark:inset-ring-yellow-400/20' => $color === 'yellow', + 'bg-green-50 text-green-700 inset-ring inset-ring-green-600/20 dark:bg-green-400/10 dark:text-green-400 dark:inset-ring-green-500/20' => $color === 'green', + 'bg-blue-50 text-blue-700 inset-ring inset-ring-blue-700/10 dark:bg-blue-400/10 dark:text-blue-400 dark:inset-ring-blue-400/30' => $color === 'blue', + 'bg-indigo-50 text-indigo-700 inset-ring inset-ring-indigo-700/10 dark:bg-indigo-400/10 dark:text-indigo-400 dark:inset-ring-indigo-400/30' => $color === 'indigo', + 'bg-purple-50 text-purple-700 inset-ring inset-ring-purple-700/10 dark:bg-purple-400/10 dark:text-purple-400 dark:inset-ring-purple-400/30' => $color === 'purple', + 'bg-pink-50 text-pink-700 inset-ring inset-ring-pink-700/10 dark:bg-pink-400/10 dark:text-pink-400 dark:inset-ring-pink-400/20' => $color === 'pink', +])> + {{ $slot }} + + + + diff --git a/resources/views/components/modal-danger.blade.php b/resources/views/components/modal-danger.blade.php new file mode 100644 index 0000000..89b1dfa --- /dev/null +++ b/resources/views/components/modal-danger.blade.php @@ -0,0 +1,49 @@ +@props(['id', 'openButton', 'heading', 'affirmativeButton', 'formMethod', 'formAction']) + + + + + +
+ + +
+
+ +
+
+

{{ $heading }}

+
+

merge(['class'=>'text-sm text-gray-500 dark:text-gray-400']) }}>{{ $slot }}

+
+
+
+
+ + +
+
+
+
+
+
diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php index a2aaca5..7827459 100644 --- a/resources/views/welcome.blade.php +++ b/resources/views/welcome.blade.php @@ -52,9 +52,26 @@
- - News Story - + @foreach($newsStories as $story) + + {{ $story->headline }} + +

+ Published: {{ $story->start_publication_date->format('m/d/Y') ?? $story->created_at->format('m/d/Y') }}

+ @if($story->created_at->format('m/d/Y') != $story->updated_at->format('m/d/Y')) +

Last Updated: {{ $story->updated_at->format('m/d/Y') }}

+ @endif +
+ {{ $story->body }} +
+
+ @endforeach + @if($newsStories->hasPages()) + + See More Stories + {{ $newsStories->links() }} + + @endif
diff --git a/routes/web.php b/routes/web.php index 193840e..758be45 100644 --- a/routes/web.php +++ b/routes/web.php @@ -26,5 +26,5 @@ Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function () Route::get('/', [UsersController::class, 'index'])->name('index'); }); Route::resource('/etudes', AuditionEtudeController::class)->names('etudes'); - Route::resource('/news', NewsStoryController::class)->names('news'); + Route::resource('/news', NewsStoryController::class)->except(['show'])->names('news'); });