Merge branch 'refs/heads/news-stories'
This commit is contained in:
commit
bd9a6de959
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum StoryStatusEnum: string
|
||||
{
|
||||
case DRAFT = 'draft';
|
||||
case SCHEDULED = 'scheduled';
|
||||
case PUBLISHED = 'published';
|
||||
case EXPIRED = 'expired';
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::DRAFT => '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',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\NewsStoryRequest;
|
||||
use App\Models\NewsStory;
|
||||
|
||||
class NewsStoryController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$stories = NewsStory::orderBy('id', 'desc')->paginate(15);
|
||||
|
||||
return view('admin.news.index', compact('stories'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
return view('admin.news.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(NewsStoryRequest $request)
|
||||
{
|
||||
NewsStory::create(
|
||||
$request->validated()
|
||||
);
|
||||
|
||||
return redirect()->route('admin.news.index')->with('success', 'Story Added Successfully');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
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(NewsStoryRequest $request, string $id)
|
||||
{
|
||||
$newsStory = NewsStory::findOrFail($id);
|
||||
$newsStory->update($request->validated());
|
||||
|
||||
return redirect()->route('admin.news.index')->with('success', 'Story Updated Successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
$story = NewsStory::findOrFail($id);
|
||||
$story->delete();
|
||||
|
||||
return redirect()->route('admin.news.index')->with('success', 'Story Deleted Successfully');
|
||||
}
|
||||
}
|
||||
|
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class NewsStoryRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->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, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,10 @@ class Admin extends Component
|
|||
'name' => 'Audition Etudes',
|
||||
'link' => route('admin.etudes.index'),
|
||||
],
|
||||
[
|
||||
'name' => 'News Stories',
|
||||
'link' => route('admin.news.index'),
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\NewsStory;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class NewsStoryFactory extends Factory
|
||||
{
|
||||
protected $model = NewsStory::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'headline' => $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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('news_stories', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('headline');
|
||||
$table->text('body');
|
||||
$table->date('start_publication_date')->nullable();
|
||||
$table->date('stop_publication_date')->nullable();
|
||||
$table->string('active');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('news_stories');
|
||||
}
|
||||
};
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
@import 'tailwindcss';
|
||||
|
||||
@layer base {
|
||||
|
||||
dl dt {
|
||||
@apply font-semibold;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
<x-layout.admin>
|
||||
<x-card class="max-w-3xl mx-auto">
|
||||
<x-slot:header class="bg-brand-600!">Create News Story</x-slot:header>
|
||||
|
||||
<x-slot:body class="bg-white border border-brand-600">
|
||||
@if($errors->any())
|
||||
@foreach($errors->all() as $error)
|
||||
<x-alert color="red">{{ $error }}</x-alert>
|
||||
@endforeach
|
||||
@endif
|
||||
<x-form method="POST" :action="route('admin.news.store')">
|
||||
|
||||
<div>
|
||||
<x-form.input name="headline" label="Headline"/>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<x-form.textarea name="body" label="Body"/>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<x-form.radio-group name="active">
|
||||
<x-slot:label>Status</x-slot:label>
|
||||
<x-form.radio-group-item id="active" value="true" checked>Active</x-form.radio-group-item>
|
||||
<x-form.radio-group-item id="draft" value="false">Draft</x-form.radio-group-item>
|
||||
</x-form.radio-group>
|
||||
</div>
|
||||
|
||||
<div x-data="{ showStartDate: false }" class="mt-3">
|
||||
<x-form.checkbox name="scheduleStart" x-model="showStartDate" value="true"
|
||||
label="Schedule Publication"/>
|
||||
<x-form.input
|
||||
type="date"
|
||||
name="start_publication_date"
|
||||
label=""
|
||||
:value="now()->toDateString()"
|
||||
x-show="showStartDate"
|
||||
x-cloak
|
||||
/>
|
||||
</div>
|
||||
<div x-data="{ showEndDate: false }" class="mt-3">
|
||||
<x-form.checkbox name="scheduleEnd" x-model="showEndDate" value="true" label="Schedule Removal"/>
|
||||
<x-form.input
|
||||
type="date"
|
||||
name="stop_publication_date"
|
||||
label=""
|
||||
x-show="showEndDate"
|
||||
x-cloak
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-3 text-right">
|
||||
<x-form.button type="submit">Save Story</x-form.button>
|
||||
</div>
|
||||
</x-form>
|
||||
</x-slot:body>
|
||||
</x-card>
|
||||
</x-layout.admin>
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<x-layout.admin>
|
||||
<x-card class="max-w-3xl mx-auto">
|
||||
<x-slot:header class="bg-brand-600!">Create News Story</x-slot:header>
|
||||
|
||||
<x-slot:body class="bg-white border border-brand-600">
|
||||
@if($errors->any())
|
||||
@foreach($errors->all() as $error)
|
||||
<x-alert color="red">{{ $error }}</x-alert>
|
||||
@endforeach
|
||||
@endif
|
||||
<x-form method="PATCH" :action="route('admin.news.update', $newsStory)">
|
||||
<div>
|
||||
<x-form.input name="headline" label="Headline" value="{{ $newsStory->headline }}" />
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<x-form.textarea name="body" label="Body">
|
||||
{{ $newsStory->body }}
|
||||
</x-form.textarea>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<x-form.radio-group name="active">
|
||||
<x-slot:label>Status</x-slot:label>
|
||||
@if($newsStory->active)
|
||||
<x-form.radio-group-item id="active" value="true" checked>Active</x-form.radio-group-item>
|
||||
<x-form.radio-group-item id="draft" value="false">Draft</x-form.radio-group-item>
|
||||
@else
|
||||
<x-form.radio-group-item id="active" value="true">Active</x-form.radio-group-item>
|
||||
<x-form.radio-group-item id="draft" value="false" checked>Draft</x-form.radio-group-item>
|
||||
@endif
|
||||
</x-form.radio-group>
|
||||
</div>
|
||||
|
||||
<div x-data="{ showStartDate: {{ $newsStory->start_publication_date ? 'true' : 'false' }} }" class="mt-3">
|
||||
<x-form.checkbox name="scheduleStart" x-model="showStartDate" value="true"
|
||||
label="Schedule Publication"/>
|
||||
<x-form.input
|
||||
type="date"
|
||||
name="start_publication_date"
|
||||
label=""
|
||||
value="{{ $newsStory->start_publication_date?->toDateString() ?? now()->toDateString() }}"
|
||||
x-show="showStartDate"
|
||||
x-cloak
|
||||
/>
|
||||
</div>
|
||||
<div x-data="{ showEndDate: {{ $newsStory->stop_publication_date ? 'true' : 'false' }} }" class="mt-3">
|
||||
<x-form.checkbox name="scheduleEnd" x-model="showEndDate" value="true" label="Schedule Removal"/>
|
||||
<x-form.input
|
||||
type="date"
|
||||
name="stop_publication_date"
|
||||
label=""
|
||||
value="{{ $newsStory->stop_publication_date?->toDateString() ?? '' }}"
|
||||
x-show="showEndDate"
|
||||
x-cloak
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-3 text-right">
|
||||
<x-form.button type="submit">Save Story</x-form.button>
|
||||
</div>
|
||||
</x-form>
|
||||
</x-slot:body>
|
||||
</x-card>
|
||||
</x-layout.admin>
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<x-layout.admin>
|
||||
<x-card class="">
|
||||
<x-slot:header class="bg-brand-600!">News Stories</x-slot:header>
|
||||
<x-slot:body class="bg-white border border-brand-600">
|
||||
<div class="text-right">
|
||||
<x-form.button type="link" :href="route('admin.news.create')">New Story</x-form.button>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<x-table>
|
||||
<x-slot:header>
|
||||
<x-table.th></x-table.th>
|
||||
<x-table.th>Headline</x-table.th>
|
||||
<x-table.th>Status</x-table.th>
|
||||
<x-table.th>Submission Date</x-table.th>
|
||||
<x-table.th>Start Publication</x-table.th>
|
||||
<x-table.th>Stop Publication</x-table.th>
|
||||
</x-slot:header>
|
||||
@foreach($stories as $story)
|
||||
<tr>
|
||||
<x-table.td class="flex gap-3 ">
|
||||
<a href="{{route('admin.news.edit', $story)}}">
|
||||
<x-heroicon-s-pencil-square class="size-5"/>
|
||||
</a>
|
||||
<x-modal-danger id="delete-{{$story->id}}" form-method="DELETE" form-action="{{ route('admin.news.destroy', $story) }}">
|
||||
<x-slot:heading>Confirm Story Deletion</x-slot:heading>
|
||||
<x-slot:affirmativeButton type="submit">Delete</x-slot:affirmativeButton>
|
||||
<x-slot:openButton><x-heroicon-s-trash class="size-5" /></x-slot:openButton>
|
||||
Do you really want to delete the story with the headline<br />{{ $story->headline }}?
|
||||
</x-modal-danger>
|
||||
</x-table.td>
|
||||
<x-table.td>{{ $story->headline }}</x-table.td>
|
||||
<x-table.td>
|
||||
<x-badge-pill :color="$story->status->color()">
|
||||
{{ $story->status->label() }}
|
||||
</x-badge-pill>
|
||||
</x-table.td>
|
||||
<x-table.td>{{ $story->created_at->format('m/d/Y') }}</x-table.td>
|
||||
<x-table.td>{{ $story->start_publication_date->format('m/d/Y') }}</x-table.td>
|
||||
<x-table.td>{{ $story->stop_publication_date?->format('m/d/Y') }}</x-table.td>
|
||||
|
||||
</tr>
|
||||
@endforeach
|
||||
</x-table>
|
||||
</div>
|
||||
</x-slot:body>
|
||||
</x-card>
|
||||
</x-layout.admin>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
@props(['color' => 'blue'])
|
||||
<div @class([
|
||||
'border-l-4 p-2 mb-3 ',
|
||||
'border-yellow-400 bg-yellow-50 dark:border-yellow-500 dark:bg-yellow-500/10' => $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',
|
||||
])>
|
||||
<div class="flex">
|
||||
<div class="shrink-0">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" data-slot="icon" aria-hidden="true"
|
||||
@class([
|
||||
'size-5 ',
|
||||
'text-yellow-400 dark:text-yellow-500' => $color === 'yellow',
|
||||
'text-red-400 dark:text-red-500' => $color === 'red',
|
||||
'text-green-400 dark:text-green-500' => $color === 'green',
|
||||
'text-blue-400 dark:text-blue-500' => $color === 'blue',
|
||||
|
||||
])>
|
||||
<path
|
||||
d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
|
||||
clip-rule="evenodd" fill-rule="evenodd"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p @class([
|
||||
'text-sm ',
|
||||
'text-yellow-700 dar:text-yellow-300' => $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',
|
||||
])>
|
||||
{{ $slot }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
@props(['color' => 'blue'])
|
||||
|
||||
<span @class([
|
||||
'inline-flex items-center rounded-full px-2 py-1 text-xs font-medium',
|
||||
'bg-red-50 text-red-700 inset-ring inset-ring-red-600/10 dark:bg-red-400/10 dark:text-red-400 dark:inset-ring-red-400/20' => $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 }}
|
||||
</span>
|
||||
|
||||
|
||||
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
<div class="group grid size-4 grid-cols-1">
|
||||
<input id="{{ $id ?? $name }}" type="checkbox" name="{{ $name }}"
|
||||
{{ $checked ? 'checked' : '' }}
|
||||
class="col-start-1 row-start-1 appearance-none rounded-sm border border-gray-300 bg-white checked:border-brand-600 checked:bg-brand-600 indeterminate:border-brand-600 indeterminate:bg-brand-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-600 disabled:border-gray-300 disabled:bg-gray-100 disabled:checked:bg-gray-100 dark:border-white/10 dark:bg-white/5 dark:checked:border-brand-500 dark:checked:bg-brand-500 dark:indeterminate:border-brand-500 dark:indeterminate:bg-brand-500 dark:focus-visible:outline-brand-500 dark:disabled:border-white/5 dark:disabled:bg-white/10 dark:disabled:checked:bg-white/10 forced-colors:appearance-auto"/>
|
||||
{{ $attributes->merge(['class' => 'col-start-1 row-start-1 appearance-none rounded-sm border border-gray-300 bg-white checked:border-brand-600 checked:bg-brand-600 indeterminate:border-brand-600 indeterminate:bg-brand-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-600 disabled:border-gray-300 disabled:bg-gray-100 disabled:checked:bg-gray-100 dark:border-white/10 dark:bg-white/5 dark:checked:border-brand-500 dark:checked:bg-brand-500 dark:indeterminate:border-brand-500 dark:indeterminate:bg-brand-500 dark:focus-visible:outline-brand-500 dark:disabled:border-white/5 dark:disabled:bg-white/10 dark:disabled:checked:bg-white/10 forced-colors:appearance-auto']) }}/>
|
||||
<svg viewBox="0 0 14 14" fill="none"
|
||||
class="pointer-events-none col-start-1 row-start-1 size-3.5 self-center justify-self-center stroke-white group-has-disabled:stroke-gray-950/25 dark:group-has-disabled:stroke-white/25">
|
||||
<path d="M3 8L6 11L11 3.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
@aware(['name'])
|
||||
@props(['value', 'id'])
|
||||
<div class="flex items-center">
|
||||
<input type="radio" name="{{ $name }}" id="{{ $id }}"
|
||||
value="{{ $value }}" {{ $attributes }}
|
||||
class="relative size-4 appearance-none rounded-full border border-gray-300 bg-white before:absolute before:inset-1 before:rounded-full before:bg-white not-checked:before:hidden checked:border-indigo-600 checked:bg-indigo-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:border-gray-300 disabled:bg-gray-100 disabled:before:bg-gray-400 dark:border-white/10 dark:bg-white/5 dark:checked:border-indigo-500 dark:checked:bg-indigo-500 dark:focus-visible:outline-indigo-500 dark:disabled:border-white/5 dark:disabled:bg-white/10 dark:disabled:before:bg-white/20 forced-colors:appearance-auto forced-colors:before:hidden"/>
|
||||
<label for="{{ $id }}" class="ml-3 block text-sm/6 font-medium text-gray-900 dark:text-white">{{ $slot }}</label>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
@props(['label' => null, 'sublabel' => null, 'name'])
|
||||
<fieldset>
|
||||
@if($label)
|
||||
<legend class="text-sm/6 font-semibold text-gray-900 dark:text-white">{{ $label }}</legend>
|
||||
@endif
|
||||
@if($sublabel)
|
||||
<p class="mt-1 text-sm/6 text-gray-600 dark:text-gray-400">{{ $sublabel }}</p>
|
||||
@endif
|
||||
<div class="mt-3 space-y-2">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
@props(['id', 'openButton', 'heading', 'affirmativeButton', 'formMethod', 'formAction'])
|
||||
<button command="show-modal" commandfor="{{ $id }}"
|
||||
class="rounded-md bg-gray-950/5 px-2.5 py-1.5 text-sm font-semibold text-gray-900 hover:bg-gray-950/10 dark:bg-white/10 dark:text-white dark:inset-ring dark:inset-ring-white/5 dark:hover:bg-white/20">
|
||||
{{ $openButton }}
|
||||
</button>
|
||||
<el-dialog>
|
||||
<dialog id="{{ $id }}" aria-labelledby="dialog-title"
|
||||
class="fixed inset-0 size-auto max-h-none max-w-none overflow-y-auto bg-transparent backdrop:bg-transparent">
|
||||
<el-dialog-backdrop
|
||||
class="fixed inset-0 bg-gray-500/75 transition-opacity data-closed:opacity-0 data-enter:duration-300 data-enter:ease-out data-leave:duration-200 data-leave:ease-in dark:bg-gray-900/50"></el-dialog-backdrop>
|
||||
|
||||
<div tabindex="0"
|
||||
class="flex min-h-full items-end justify-center p-4 text-center focus:outline-none sm:items-center sm:p-0">
|
||||
<el-dialog-panel
|
||||
class="relative transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all data-closed:translate-y-4 data-closed:opacity-0 data-enter:duration-300 data-enter:ease-out data-leave:duration-200 data-leave:ease-in sm:my-8 sm:w-full sm:max-w-lg sm:p-6 data-closed:sm:translate-y-0 data-closed:sm:scale-95 dark:bg-gray-800 dark:outline dark:-outline-offset-1 dark:outline-white/10">
|
||||
<x-form method="{{$formMethod}}" action="{{$formAction}}">
|
||||
<div class="sm:flex sm:items-start">
|
||||
<div
|
||||
class="mx-auto flex size-12 shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:size-10 dark:bg-red-500/10">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"
|
||||
data-slot="icon" aria-hidden="true" class="size-6 text-red-600 dark:text-red-400">
|
||||
<path
|
||||
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z"
|
||||
stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<h3 id="dialog-title"
|
||||
class="text-base font-semibold text-gray-900 dark:text-white">{{ $heading }}</h3>
|
||||
<div class="mt-2">
|
||||
<p {{ $attributes->merge(['class'=>'text-sm text-gray-500 dark:text-gray-400']) }}>{{ $slot }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button type="submit"
|
||||
class="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-red-500 sm:ml-3 sm:w-auto dark:bg-red-500 dark:shadow-none dark:hover:bg-red-400">
|
||||
{{ $affirmativeButton }}
|
||||
</button>
|
||||
<button type="button" command="close" commandfor="{{ $id }}"
|
||||
class="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-xs inset-ring-1 inset-ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto dark:bg-white/10 dark:text-white dark:shadow-none dark:inset-ring-white/5 dark:hover:bg-white/20">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</x-form>
|
||||
</el-dialog-panel>
|
||||
</div>
|
||||
</dialog>
|
||||
</el-dialog>
|
||||
|
|
@ -52,9 +52,26 @@
|
|||
</x-card>
|
||||
|
||||
<div class="sm:col-span-2 lg:col-start-2 mt-5">
|
||||
<x-card>
|
||||
<x-slot:header>News Story</x-slot:header>
|
||||
@foreach($newsStories as $story)
|
||||
<x-card class="mb-5 ">
|
||||
<x-slot:header>{{ $story->headline }}</x-slot:header>
|
||||
|
||||
<p class="text-xs italic">
|
||||
Published: {{ $story->start_publication_date->format('m/d/Y') ?? $story->created_at->format('m/d/Y') }}</p>
|
||||
@if($story->created_at->format('m/d/Y') != $story->updated_at->format('m/d/Y'))
|
||||
<p class="mt-1 text-xs italic">Last Updated: {{ $story->updated_at->format('m/d/Y') }}</p>
|
||||
@endif
|
||||
<div class="mt-3">
|
||||
{{ $story->body }}
|
||||
</div>
|
||||
</x-card>
|
||||
@endforeach
|
||||
@if($newsStories->hasPages())
|
||||
<x-card>
|
||||
<x-slot:header>See More Stories</x-slot:header>
|
||||
{{ $newsStories->links() }}
|
||||
</x-card>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use App\Http\Controllers\Admin\AuditionEtudeController;
|
||||
use App\Http\Controllers\Admin\DashboardController;
|
||||
use App\Http\Controllers\Admin\NewsStoryController;
|
||||
use App\Http\Controllers\Admin\SiteDataController;
|
||||
use App\Http\Controllers\Admin\UsersController;
|
||||
use App\Http\Controllers\AuditionInformationPageController;
|
||||
|
|
@ -25,4 +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)->except(['show'])->names('news');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue