diff --git a/app/Filament/Resources/Articles/ArticleResource.php b/app/Filament/Resources/Articles/ArticleResource.php new file mode 100644 index 000000000..de4b5174a --- /dev/null +++ b/app/Filament/Resources/Articles/ArticleResource.php @@ -0,0 +1,39 @@ + ListArticles::route('/') + ]; + } +} diff --git a/app/Filament/Resources/Articles/Pages/ListArticles.php b/app/Filament/Resources/Articles/Pages/ListArticles.php new file mode 100644 index 000000000..2b985ff8d --- /dev/null +++ b/app/Filament/Resources/Articles/Pages/ListArticles.php @@ -0,0 +1,16 @@ +defaultSort('submitted_at', 'desc') + ->openRecordUrlInNewTab() + ->columns([ + ImageColumn::make('authorRelation.github_id') + ->label('Author') + ->circular() + ->width('1%') + ->defaultImageUrl(fn(?string $state): string => $state ? sprintf('https://avatars.githubusercontent.com/u/%s', $state) : asset('images/laravelio-icon-gray.svg')), + + TextColumn::make('authorRelation.name') + ->label('') + ->description(fn(Article $article): ?string => $article->authorRelation->username), + + TextColumn::make('title') + ->searchable(['title', 'slug', 'body']), + + TextColumn::make('submitted_at') + ->label('Submitted on') + ->dateTime() + ->sortable(), + ]) + ->filters([ + Filter::make('awaiting_approvals') + ->query(fn(Builder $query): Builder => $query->awaitingApproval()) + ->default() + ]) + ->recordActions([ + Action::make('view') + ->url(fn(Article $article): string => route('articles.show', $article->slug())) + ->openUrlInNewTab() + ->icon('heroicon-s-eye'), + ]) + ->toolbarActions([ + // + ]); + } +} diff --git a/app/Filament/Resources/Replies/Pages/ListReplies.php b/app/Filament/Resources/Replies/Pages/ListReplies.php new file mode 100644 index 000000000..26e03bac8 --- /dev/null +++ b/app/Filament/Resources/Replies/Pages/ListReplies.php @@ -0,0 +1,18 @@ + ListReplies::route('/'), + ]; + } + + public static function getRecordRouteBindingEloquentQuery(): Builder + { + return parent::getRecordRouteBindingEloquentQuery() + ->withoutGlobalScopes([ + SoftDeletingScope::class, + ]); + } +} diff --git a/app/Filament/Resources/Replies/Tables/RepliesTable.php b/app/Filament/Resources/Replies/Tables/RepliesTable.php new file mode 100644 index 000000000..8f4736993 --- /dev/null +++ b/app/Filament/Resources/Replies/Tables/RepliesTable.php @@ -0,0 +1,85 @@ +defaultSort('updated_at', 'desc') + ->openRecordUrlInNewTab() + ->columns([ + ImageColumn::make('authorRelation.github_id') + ->label('Author') + ->circular() + ->defaultImageUrl(fn(?string $state): string => $state ? sprintf('https://avatars.githubusercontent.com/u/%s', $state) : asset('images/laravelio-icon-gray.svg')), + + TextColumn::make('authorRelation.name') + ->label('') + ->description(fn(Reply $reply): ?string => $reply->authorRelation->username), + + TextColumn::make('replyAbleRelation.subject') + ->label('Thread') + ->searchable(), + + TextColumn::make('body') + ->label('Content') + ->limit(250) + ->wrap() + ->searchable(), + + IconColumn::make('updated_by') + ->label('Updated') + ->boolean() + ->default(false), + + TextColumn::make('created_at') + ->label('Created on') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + + TextColumn::make('updated_at') + ->label('Last updated on') + ->dateTime() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->filters([ + TrashedFilter::make(), + + TernaryFilter::make('updated_by') + ->label('Updated') + ->nullable() + ]) + ->recordActions([ + Action::make('view') + ->url(fn(Reply $reply): string => route('thread', $reply->replyAble()->slug()) . '#' . $reply->id()) + ->openUrlInNewTab() + ->icon('heroicon-s-eye'), + + DeleteAction::make(), + ]) + ->toolbarActions([ + BulkActionGroup::make([ + DeleteBulkAction::make(), + ForceDeleteBulkAction::make(), + RestoreBulkAction::make(), + ]), + ]); + } +} diff --git a/app/Filament/Resources/Users/Pages/ListUsers.php b/app/Filament/Resources/Users/Pages/ListUsers.php new file mode 100644 index 000000000..9de7f5b08 --- /dev/null +++ b/app/Filament/Resources/Users/Pages/ListUsers.php @@ -0,0 +1,18 @@ +defaultSort('created_at', 'desc') + ->openRecordUrlInNewTab() + ->columns([ + ImageColumn::make('github_id') + ->label('Name') + ->circular() + ->width('0%') + ->defaultImageUrl(fn(?string $state): string => $state ? sprintf('https://avatars.githubusercontent.com/u/%s', $state) : asset('images/laravelio-icon-gray.svg')), + + TextColumn::make('username') + ->label('') + ->searchable() + ->formatStateUsing(fn(User $user): ?string => $user->name) + ->description(fn(User $user): ?string => $user->username), + + TextColumn::make('email') + ->searchable() + ->toggleable(isToggledHiddenByDefault: true), + + TextColumn::make('type') + ->label('Role') + ->badge() + ->formatStateUsing(fn(string $state): string => match ($state) { + '1' => 'User', + '2' => 'Moderator', + '3' => 'Admin', + }), + + IconColumn::make('banned_at') + ->label('Banned') + ->boolean() + ->default(false), + + TextColumn::make('created_at') + ->label('Joined on') + ->dateTime() + ->sortable() + ]) + ->filters([ + SelectFilter::make('type') + ->options([ + '1' => 'User', + '2' => 'Moderator', + '3' => 'Admin', + ]), + + TernaryFilter::make('banned_at') + ->label('Banned') + ->nullable() + ]) + ->recordActions([ + Action::make('view') + ->url(fn(User $user): string => route('profile', $user->username)) + ->openUrlInNewTab() + ->icon('heroicon-s-eye'), + + ActionGroup::make([ + Action::make('verify_author') + ->action(function (User $user) { + VerifyAuthor::dispatchSync($user); + + Notification::make() + ->title($user->name . ' is now a verified author.') + ->success() + ->send(); + }) + ->openUrlInNewTab() + ->color('primary') + ->icon('heroicon-s-check-circle') + ->requiresConfirmation() + ->visible(fn(User $user): bool => auth()->user()->can(UserPolicy::ADMIN, $user) && ! $user->isVerifiedAuthor()), + + Action::make('unverify_author') + ->action(function (User $user) { + UnVerifyAuthor::dispatchSync($user); + + Notification::make() + ->title($user->name . '\'s threads have been deleted.') + ->success() + ->send(); + }) + ->openUrlInNewTab() + ->color('danger') + ->icon('heroicon-s-x-circle') + ->requiresConfirmation() + ->visible(fn(User $user): bool => auth()->user()->can(UserPolicy::ADMIN, $user) && $user->isVerifiedAuthor()), + + Action::make('ban_author') + ->schema([ + TextInput::make('reason') + ->label('Reason') + ->required() + ->placeholder('Provide a reason for banning this user...'), + + Checkbox::make('delete_threads') + ->label('Delete all threads') + ->default(false), + ]) + ->action(function (User $user, array $data) { + BanUser::dispatchSync($user, $data['reason']); + + if ($data['delete_threads']) { + DeleteUserThreads::dispatchSync($user); + } + + Notification::make() + ->title( + $user->name . ' is now banned.' . ($data['delete_threads'] ? ' And all his threads are now deleted.' : '') + ) + ->success() + ->send(); + }) + ->modalDescription('Are you sure you\'d like to ban this user? This will prevent him from logging in, posting threads and replying to threads.') + ->openUrlInNewTab() + ->color('danger') + ->icon('heroicon-s-check-circle') + ->requiresConfirmation() + ->visible(fn(User $user): bool => auth()->user()->can(UserPolicy::BAN, $user) && ! $user->isBanned()), + + Action::make('unban_author') + ->action(function (User $user) { + + UnbanUser::dispatchSync($user); + + Notification::make() + ->title($user->name . ' is no longer a banned user.') + ->success() + ->send(); + }) + ->openUrlInNewTab() + ->color('primary') + ->icon('heroicon-s-x-circle') + ->requiresConfirmation() + ->visible(fn(User $user): bool => auth()->user()->can(UserPolicy::BAN, $user) && $user->isBanned()), + + Action::make('delete_threads') + ->action(function (User $user) { + DeleteUserThreads::dispatchSync($user); + + Notification::make() + ->title($user->name . '\'s threads have been deleted.') + ->success() + ->send(); + }) + ->openUrlInNewTab() + ->color('danger') + ->icon('heroicon-s-archive-box-x-mark') + ->requiresConfirmation() + ->visible(fn(User $user): bool => auth()->user()->can(UserPolicy::DELETE, $user)), + + DeleteAction::make() + ->visible(fn(User $user): bool => auth()->user()->can(UserPolicy::DELETE, $user)), + ]), + ]) + ->toolbarActions([ + // + ]); + } +} diff --git a/app/Filament/Resources/Users/UserResource.php b/app/Filament/Resources/Users/UserResource.php new file mode 100644 index 000000000..34f784bfa --- /dev/null +++ b/app/Filament/Resources/Users/UserResource.php @@ -0,0 +1,39 @@ + ListUsers::route('/'), + ]; + } +} diff --git a/app/Filament/Widgets/ArticlesStatsOverview.php b/app/Filament/Widgets/ArticlesStatsOverview.php new file mode 100644 index 000000000..50d340738 --- /dev/null +++ b/app/Filament/Widgets/ArticlesStatsOverview.php @@ -0,0 +1,66 @@ +remember( + "widgets:articles:total", + now()->addSeconds($cacheTtlSeconds), + fn() => Article::query()->count(), + ); + + $publishedTotal = cache()->remember( + "widgets:articles:published:total", + now()->addSeconds($cacheTtlSeconds), + fn() => Article::query()->published()->count(), + ); + + $publishedWindow = cache()->remember( + "widgets:articles:published:{$window}", + now()->addSeconds($cacheTtlSeconds), + fn() => Article::query() + ->published() + ->where('submitted_at', '>=', now()->subDays($window)) + ->count(), + ); + + $awaiting = cache()->remember( + "widgets:articles:awaiting", + now()->addSeconds(60), + fn() => Article::query()->awaitingApproval()->count(), + ); + + return [ + Stat::make('Articles', number_format($total)) + ->description('Total articles') + ->icon('heroicon-o-newspaper'), + + Stat::make('Published', number_format($publishedTotal)) + ->description('All-time approved') + ->color('success') + ->icon('heroicon-o-check-circle'), + + Stat::make("Published ({$window}d)", number_format($publishedWindow)) + ->description("Approved in last {$window} days") + ->color('info') + ->icon('heroicon-o-check-badge'), + + Stat::make('Awaiting Approval', number_format($awaiting)) + ->description('Submitted but not approved') + ->color('warning') + ->icon('heroicon-o-clock'), + ]; + } +} diff --git a/app/Filament/Widgets/ArticlesTrendChart.php b/app/Filament/Widgets/ArticlesTrendChart.php new file mode 100644 index 000000000..deb925453 --- /dev/null +++ b/app/Filament/Widgets/ArticlesTrendChart.php @@ -0,0 +1,42 @@ +between( + start: now()->startOfYear(), + end: now()->endOfYear(), + ) + ->perMonth() + ->count(); + + return [ + 'datasets' => [ + [ + 'label' => 'Submitted', + 'data' => $data->map(fn(TrendValue $value) => $value->aggregate), + 'tension' => 0.35, + ], + ], + 'labels' => $data->map(fn(TrendValue $value) => Carbon::parse($value->date)->format('M Y')), + ]; + } +} diff --git a/app/Filament/Widgets/RepliesStatsOverview.php b/app/Filament/Widgets/RepliesStatsOverview.php new file mode 100644 index 000000000..1b8b3cb7e --- /dev/null +++ b/app/Filament/Widgets/RepliesStatsOverview.php @@ -0,0 +1,52 @@ +remember( + 'widgets:replies:total', + now()->addSeconds($cacheTtlSeconds), + fn() => Reply::query()->count(), + ); + + $lastWindow = cache()->remember( + "widgets:replies:new:{$window}", + now()->addSeconds($cacheTtlSeconds), + fn() => Reply::query()->where('created_at', '>=', now()->subDays($window))->count(), + ); + + $solutions = cache()->remember( + 'widgets:replies:solutions', + now()->addSeconds($cacheTtlSeconds), + fn() => Reply::query()->isSolution()->count(), + ); + + return [ + Stat::make('Replies', number_format($total)) + ->description('Total replies') + ->icon('heroicon-o-chat-bubble-left-right'), + + Stat::make("New ({$window}d)", number_format($lastWindow)) + ->description("Replies in last {$window} days") + ->color('info') + ->icon('heroicon-o-chat-bubble-left-ellipsis'), + + Stat::make('Solutions', number_format($solutions)) + ->description('Marked as solution') + ->color('success') + ->icon('heroicon-o-sparkles'), + ]; + } +} diff --git a/app/Filament/Widgets/RepliesTrendChart.php b/app/Filament/Widgets/RepliesTrendChart.php new file mode 100644 index 000000000..0fa74870a --- /dev/null +++ b/app/Filament/Widgets/RepliesTrendChart.php @@ -0,0 +1,42 @@ +between( + start: now()->startOfYear(), + end: now()->endOfYear(), + ) + ->perMonth() + ->count(); + + return [ + 'datasets' => [ + [ + 'label' => 'Submitted', + 'data' => $data->map(fn(TrendValue $value) => $value->aggregate), + 'tension' => 0.35, + ], + ], + 'labels' => $data->map(fn(TrendValue $value) => Carbon::parse($value->date)->format('M Y')), + ]; + } +} diff --git a/app/Filament/Widgets/UsersStatsOverview.php b/app/Filament/Widgets/UsersStatsOverview.php new file mode 100644 index 000000000..4e60bc19c --- /dev/null +++ b/app/Filament/Widgets/UsersStatsOverview.php @@ -0,0 +1,53 @@ +remember( + 'widgets:users:total', + now()->addSeconds($cacheTtlSeconds), + fn() => User::query()->count(), + ); + + $lastWindow = cache()->remember( + "widgets:users:new:{$window}", + now()->addSeconds($cacheTtlSeconds), + fn() => User::query()->where('created_at', '>=', now()->subDays($window))->count(), + ); + + $verified = cache()->remember( + 'widgets:users:verified', + now()->addSeconds($cacheTtlSeconds), + fn() => User::query()->whereNotNull('author_verified_at')->count(), + ); + + return [ + Stat::make('Users', number_format($total)) + ->description('Total registered') + ->icon('heroicon-o-users'), + + Stat::make("New ({$window}d)", number_format($lastWindow)) + ->description("Joined in last {$window} days") + ->color('info') + ->icon('heroicon-o-user-plus'), + + Stat::make('Verified Authors', number_format($verified)) + ->description('Users verified as authors') + ->color('success') + ->icon('heroicon-o-check'), + ]; + } +} diff --git a/app/Filament/Widgets/UsersTrendChart.php b/app/Filament/Widgets/UsersTrendChart.php new file mode 100644 index 000000000..680edb9d9 --- /dev/null +++ b/app/Filament/Widgets/UsersTrendChart.php @@ -0,0 +1,42 @@ +between( + start: now()->startOfYear(), + end: now()->endOfYear(), + ) + ->perMonth() + ->count(); + + return [ + 'datasets' => [ + [ + 'label' => 'Submitted', + 'data' => $data->map(fn(TrendValue $value) => $value->aggregate), + 'tension' => 0.35, + ], + ], + 'labels' => $data->map(fn(TrendValue $value) => Carbon::parse($value->date)->format('M Y')), + ]; + } +} diff --git a/app/Http/Controllers/Admin/ArticlesController.php b/app/Http/Controllers/Admin/ArticlesController.php index a8433dc1f..8d928645e 100644 --- a/app/Http/Controllers/Admin/ArticlesController.php +++ b/app/Http/Controllers/Admin/ArticlesController.php @@ -9,10 +9,8 @@ use App\Jobs\DisapproveArticle; use App\Models\Article; use App\Policies\ArticlePolicy; -use App\Queries\SearchArticles; use Illuminate\Auth\Middleware\Authenticate; use Illuminate\Http\RedirectResponse; -use Illuminate\View\View; class ArticlesController extends Controller { @@ -21,19 +19,6 @@ public function __construct() $this->middleware([Authenticate::class, VerifyAdmins::class]); } - public function index(): View - { - if ($adminSearch = request('admin_search')) { - $articles = SearchArticles::get($adminSearch)->appends(['admin_search' => $adminSearch]); - } else { - $articles = Article::awaitingApproval() - ->orderByDesc('submitted_at') - ->paginate(); - } - - return view('admin.articles', compact('articles', 'adminSearch')); - } - public function approve(Article $article): RedirectResponse { $this->authorize(ArticlePolicy::APPROVE, $article); diff --git a/app/Http/Controllers/Admin/RepliesController.php b/app/Http/Controllers/Admin/RepliesController.php deleted file mode 100644 index 50f431a96..000000000 --- a/app/Http/Controllers/Admin/RepliesController.php +++ /dev/null @@ -1,21 +0,0 @@ -appends(['admin_search' => $adminSearch]); - } else { - $replies = Reply::with('replyAbleRelation')->orderByDesc('updated_at')->paginate(); - } - - return view('admin.replies', compact('replies', 'adminSearch')); - } -} diff --git a/app/Http/Controllers/Admin/UsersController.php b/app/Http/Controllers/Admin/UsersController.php index f274e2393..e03c2ecf5 100644 --- a/app/Http/Controllers/Admin/UsersController.php +++ b/app/Http/Controllers/Admin/UsersController.php @@ -6,17 +6,12 @@ use App\Http\Middleware\VerifyAdmins; use App\Http\Requests\BanRequest; use App\Jobs\BanUser; -use App\Jobs\DeleteUser; use App\Jobs\DeleteUserThreads; use App\Jobs\UnbanUser; -use App\Jobs\UnVerifyAuthor; -use App\Jobs\VerifyAuthor; use App\Models\User; use App\Policies\UserPolicy; -use App\Queries\SearchUsers; use Illuminate\Auth\Middleware\Authenticate; use Illuminate\Http\RedirectResponse; -use Illuminate\View\View; class UsersController extends Controller { @@ -25,17 +20,6 @@ public function __construct() $this->middleware([Authenticate::class, VerifyAdmins::class]); } - public function index(): View - { - if ($adminSearch = request('admin_search')) { - $users = SearchUsers::get($adminSearch)->appends(['admin_search' => $adminSearch]); - } else { - $users = User::latest()->paginate(20); - } - - return view('admin.users', compact('users', 'adminSearch')); - } - public function ban(BanRequest $request, User $user): RedirectResponse { $this->authorize(UserPolicy::BAN, $user); @@ -61,48 +45,4 @@ public function unban(User $user): RedirectResponse return redirect()->route('profile', $user->username()); } - - public function verifyAuthor(User $user) - { - $this->authorize(UserPolicy::ADMIN, $user); - - $this->dispatchSync(new VerifyAuthor($user)); - - $this->success($user->name().' was verified!'); - - return redirect()->route('admin.users'); - } - - public function unverifyAuthor(User $user) - { - $this->authorize(UserPolicy::ADMIN, $user); - - $this->dispatchSync(new UnverifyAuthor($user)); - - $this->success($user->name().' was unverified!'); - - return redirect()->route('admin.users'); - } - - public function delete(User $user): RedirectResponse - { - $this->authorize(UserPolicy::DELETE, $user); - - $this->dispatchSync(new DeleteUser($user)); - - $this->success($user->name().' was deleted and all of their content was removed!'); - - return redirect()->route('admin.users'); - } - - public function deleteThreads(User $user): RedirectResponse - { - $this->authorize(UserPolicy::DELETE, $user); - - $this->dispatchSync(new DeleteUserThreads($user)); - - $this->success($user->name().' threads were deleted!'); - - return redirect()->route('admin.users'); - } } diff --git a/app/Jobs/BanUser.php b/app/Jobs/BanUser.php index 50f8288d1..b0fcb5a51 100644 --- a/app/Jobs/BanUser.php +++ b/app/Jobs/BanUser.php @@ -4,9 +4,13 @@ use App\Models\User; use Carbon\Carbon; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Foundation\Queue\Queueable; -final class BanUser +final class BanUser implements ShouldQueue { + use Queueable; + public function __construct(private User $user, private $reason) {} public function handle(): void diff --git a/app/Jobs/DeleteUserThreads.php b/app/Jobs/DeleteUserThreads.php index 445b9dc4f..1fc6865eb 100644 --- a/app/Jobs/DeleteUserThreads.php +++ b/app/Jobs/DeleteUserThreads.php @@ -3,9 +3,13 @@ namespace App\Jobs; use App\Models\User; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Foundation\Queue\Queueable; -final class DeleteUserThreads +final class DeleteUserThreads implements ShouldQueue { + use Queueable; + public function __construct(private User $user) {} public function handle(): void diff --git a/app/Jobs/UnbanUser.php b/app/Jobs/UnbanUser.php index 49b1bdfb7..2d8d0fd34 100644 --- a/app/Jobs/UnbanUser.php +++ b/app/Jobs/UnbanUser.php @@ -3,9 +3,13 @@ namespace App\Jobs; use App\Models\User; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Foundation\Queue\Queueable; -final class UnbanUser +final class UnbanUser implements ShouldQueue { + use Queueable; + public function __construct(private User $user) {} public function handle(): void diff --git a/app/Livewire/Notifications.php b/app/Livewire/UserNotifications.php similarity index 92% rename from app/Livewire/Notifications.php rename to app/Livewire/UserNotifications.php index e86e8235e..9badf6f48 100644 --- a/app/Livewire/Notifications.php +++ b/app/Livewire/UserNotifications.php @@ -10,7 +10,7 @@ use Livewire\Component; use Livewire\WithPagination; -final class Notifications extends Component +final class UserNotifications extends Component { use AuthorizesRequests; use WithPagination; @@ -22,7 +22,7 @@ public function render(): View $notifications = Auth::user()->unreadNotifications()->paginate(10); $lastPage = count($notifications) == 0 ? $notifications->lastPage() : null; - return view('livewire.notifications', [ + return view('livewire.user-notifications', [ 'notifications' => Auth::user()->unreadNotifications()->paginate(10, ['*'], 'page', $lastPage), ]); } diff --git a/app/Models/User.php b/app/Models/User.php index aeaaedcd2..1390fa740 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -5,7 +5,11 @@ use App\Concerns\HasTimestamps; use App\Concerns\PreparesSearch; use App\Enums\NotificationType; +use App\Policies\UserPolicy; use Carbon\Carbon; +use Filament\Facades\Filament; +use Filament\Models\Contracts\FilamentUser; +use Filament\Panel; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -18,7 +22,7 @@ use Laravel\Sanctum\HasApiTokens; use Laravel\Scout\Searchable; -final class User extends Authenticatable implements MustVerifyEmail +final class User extends Authenticatable implements MustVerifyEmail, FilamentUser { use HasApiTokens; use HasFactory; @@ -459,4 +463,9 @@ public function isNotificationAllowed(string $notification): bool return NotificationType::from($notificationType)->getClass() === $notification; }); } + + public function canAccessPanel(Panel $panel): bool + { + return $this->can(UserPolicy::ADMIN, User::class); + } } diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php new file mode 100644 index 000000000..a262d5a78 --- /dev/null +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -0,0 +1,78 @@ +default() + ->id('admin') + ->path('admin') + ->login() + ->colors([ + 'primary' => '#18bc9c', + ]) + ->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources') + ->discoverPages(in: app_path('Filament/Pages'), for: 'App\Filament\Pages') + ->pages([ + Dashboard::class, + ]) + ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets') + ->widgets([ + ArticlesStatsOverview::class, + UsersStatsOverview::class, + RepliesStatsOverview::class, + ArticlesTrendChart::class, + UsersTrendChart::class, + RepliesTrendChart::class, + ]) + ->middleware([ + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + AuthenticateSession::class, + ShareErrorsFromSession::class, + VerifyCsrfToken::class, + SubstituteBindings::class, + DisableBladeIconComponents::class, + DispatchServingFilamentEvent::class, + ]) + ->authMiddleware([ + Authenticate::class, + ]) + ->userMenuItems([ + Action::make('Horizon') + ->url(fn(): string => route('horizon.index')) + ->icon('heroicon-o-presentation-chart-bar') + ->openUrlInNewTab(), + ]) + ->brandLogo(asset('images/laravelio-logo.svg')) + ->unsavedChangesAlerts() + ->globalSearch(false) + ->topNavigation() + ->spa(); + } +} diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 7ad1b7b6b..e590a976d 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -1,6 +1,7 @@ =7.1", + "pragmarx/google2fa": ">=4.0" + }, + "require-dev": { + "bacon/bacon-qr-code": "^2.0", + "chillerlan/php-qrcode": "^1.0|^2.0|^3.0|^4.0", + "khanamiryan/qrcode-detector-decoder": "^1.0", + "phpunit/phpunit": "~4|~5|~6|~7|~8|~9" + }, + "suggest": { + "bacon/bacon-qr-code": "For QR Code generation, requires imagick", + "chillerlan/php-qrcode": "For QR Code generation" + }, + "type": "library", + "extra": { + "component": "package", + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PragmaRX\\Google2FAQRCode\\": "src/", + "PragmaRX\\Google2FAQRCode\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" } ], - "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", - "homepage": "http://phpseclib.sourceforge.net", + "description": "QR Code package for Google2FA", "keywords": [ - "BigInteger", - "aes", - "asn.1", - "asn1", - "blowfish", - "crypto", - "cryptography", - "encryption", - "rsa", - "security", - "sftp", - "signature", - "signing", - "ssh", - "twofish", - "x.509", - "x509" + "2fa", + "Authentication", + "Two Factor Authentication", + "google2fa", + "qr code", + "qrcode" ], "support": { - "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.46" + "issues": "https://github.com/antonioribeiro/google2fa-qrcode/issues", + "source": "https://github.com/antonioribeiro/google2fa-qrcode/tree/v3.0.0" }, - "funding": [ - { - "url": "https://github.com/terrafrost", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpseclib", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", - "type": "tidelift" - } - ], - "time": "2025-06-26T16:29:55+00:00" + "time": "2021-08-15T12:53:48+00:00" }, { "name": "predis/predis", @@ -5752,16 +7112,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.8", + "version": "v0.12.10", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "85057ceedee50c49d4f6ecaff73ee96adb3b3625" + "reference": "6e80abe6f2257121f1eb9a4c55bf29d921025b22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/85057ceedee50c49d4f6ecaff73ee96adb3b3625", - "reference": "85057ceedee50c49d4f6ecaff73ee96adb3b3625", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/6e80abe6f2257121f1eb9a4c55bf29d921025b22", + "reference": "6e80abe6f2257121f1eb9a4c55bf29d921025b22", "shasum": "" }, "require": { @@ -5811,12 +7171,11 @@ "authors": [ { "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" + "email": "justin@justinhileman.info" } ], "description": "An interactive shell for modern PHP.", - "homepage": "http://psysh.org", + "homepage": "https://psysh.org", "keywords": [ "REPL", "console", @@ -5825,9 +7184,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.8" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.10" }, - "time": "2025-03-16T03:05:19+00:00" + "time": "2025-08-04T12:39:37+00:00" }, { "name": "ralouphie/getallheaders", @@ -6076,6 +7435,162 @@ }, "time": "2022-12-10T18:12:25+00:00" }, + { + "name": "ryangjchandler/blade-capture-directive", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/ryangjchandler/blade-capture-directive.git", + "reference": "bbb1513dfd89eaec87a47fe0c449a7e3d4a1976d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ryangjchandler/blade-capture-directive/zipball/bbb1513dfd89eaec87a47fe0c449a7e3d4a1976d", + "reference": "bbb1513dfd89eaec87a47fe0c449a7e3d4a1976d", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^10.0|^11.0|^12.0", + "php": "^8.1", + "spatie/laravel-package-tools": "^1.9.2" + }, + "require-dev": { + "nunomaduro/collision": "^7.0|^8.0", + "nunomaduro/larastan": "^2.0|^3.0", + "orchestra/testbench": "^8.0|^9.0|^10.0", + "pestphp/pest": "^2.0|^3.7", + "pestphp/pest-plugin-laravel": "^2.0|^3.1", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", + "phpstan/phpstan-phpunit": "^1.0|^2.0", + "phpunit/phpunit": "^10.0|^11.5.3", + "spatie/laravel-ray": "^1.26" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "BladeCaptureDirective": "RyanChandler\\BladeCaptureDirective\\Facades\\BladeCaptureDirective" + }, + "providers": [ + "RyanChandler\\BladeCaptureDirective\\BladeCaptureDirectiveServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "RyanChandler\\BladeCaptureDirective\\": "src", + "RyanChandler\\BladeCaptureDirective\\Database\\Factories\\": "database/factories" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ryan Chandler", + "email": "support@ryangjchandler.co.uk", + "role": "Developer" + } + ], + "description": "Create inline partials in your Blade templates with ease.", + "homepage": "https://github.com/ryangjchandler/blade-capture-directive", + "keywords": [ + "blade-capture-directive", + "laravel", + "ryangjchandler" + ], + "support": { + "issues": "https://github.com/ryangjchandler/blade-capture-directive/issues", + "source": "https://github.com/ryangjchandler/blade-capture-directive/tree/v1.1.0" + }, + "funding": [ + { + "url": "https://github.com/ryangjchandler", + "type": "github" + } + ], + "time": "2025-02-25T09:09:36+00:00" + }, + { + "name": "scrivo/highlight.php", + "version": "v9.18.1.10", + "source": { + "type": "git", + "url": "https://github.com/scrivo/highlight.php.git", + "reference": "850f4b44697a2552e892ffe71490ba2733c2fc6e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/scrivo/highlight.php/zipball/850f4b44697a2552e892ffe71490ba2733c2fc6e", + "reference": "850f4b44697a2552e892ffe71490ba2733c2fc6e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.4" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.7", + "sabberworm/php-css-parser": "^8.3", + "symfony/finder": "^2.8|^3.4|^5.4", + "symfony/var-dumper": "^2.8|^3.4|^5.4" + }, + "suggest": { + "ext-mbstring": "Allows highlighting code with unicode characters and supports language with unicode keywords" + }, + "type": "library", + "autoload": { + "files": [ + "HighlightUtilities/functions.php" + ], + "psr-0": { + "Highlight\\": "", + "HighlightUtilities\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Geert Bergman", + "homepage": "http://www.scrivo.org/", + "role": "Project Author" + }, + { + "name": "Vladimir Jimenez", + "homepage": "https://allejo.io", + "role": "Maintainer" + }, + { + "name": "Martin Folkers", + "homepage": "https://twobrain.io", + "role": "Contributor" + } + ], + "description": "Server side syntax highlighter that supports 185 languages. It's a PHP port of highlight.js", + "keywords": [ + "code", + "highlight", + "highlight.js", + "highlight.php", + "syntax" + ], + "support": { + "issues": "https://github.com/scrivo/highlight.php/issues", + "source": "https://github.com/scrivo/highlight.php" + }, + "funding": [ + { + "url": "https://github.com/allejo", + "type": "github" + } + ], + "time": "2022-12-17T21:53:22+00:00" + }, { "name": "sentry/sentry", "version": "4.15.2", @@ -6256,16 +7771,16 @@ }, { "name": "spatie/backtrace", - "version": "1.7.4", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "cd37a49fce7137359ac30ecc44ef3e16404cccbe" + "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/cd37a49fce7137359ac30ecc44ef3e16404cccbe", - "reference": "cd37a49fce7137359ac30ecc44ef3e16404cccbe", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/8c0f16a59ae35ec8c62d85c3c17585158f430110", + "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110", "shasum": "" }, "require": { @@ -6303,7 +7818,8 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/backtrace/tree/1.7.4" + "issues": "https://github.com/spatie/backtrace/issues", + "source": "https://github.com/spatie/backtrace/tree/1.8.1" }, "funding": [ { @@ -6315,7 +7831,7 @@ "type": "other" } ], - "time": "2025-05-08T15:41:09+00:00" + "time": "2025-08-26T08:22:30+00:00" }, { "name": "spatie/browsershot", @@ -6679,6 +8195,65 @@ ], "time": "2025-02-21T14:31:39+00:00" }, + { + "name": "spatie/invade", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/invade.git", + "reference": "b920f6411d21df4e8610a138e2e87ae4957d7f63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/invade/zipball/b920f6411d21df4e8610a138e2e87ae4957d7f63", + "reference": "b920f6411d21df4e8610a138e2e87ae4957d7f63", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "pestphp/pest": "^1.20", + "phpstan/phpstan": "^1.4", + "spatie/ray": "^1.28" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Spatie\\Invade\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "A PHP function to work with private properties and methods", + "homepage": "https://github.com/spatie/invade", + "keywords": [ + "invade", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/invade/tree/2.1.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-05-17T09:06:10+00:00" + }, { "name": "spatie/laravel-feed", "version": "4.4.2", @@ -7205,32 +8780,98 @@ "type": "github" } ], - "time": "2024-12-09T15:05:12+00:00" + "time": "2024-12-09T15:05:12+00:00" + }, + { + "name": "spatie/robots-txt", + "version": "2.5.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/robots-txt.git", + "reference": "ef85dfaa48372c0a7fdfb144592f95de1a2e9b79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/robots-txt/zipball/ef85dfaa48372c0a7fdfb144592f95de1a2e9b79", + "reference": "ef85dfaa48372c0a7fdfb144592f95de1a2e9b79", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Robots\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brent Roose", + "email": "brent@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Determine if a page may be crawled from robots.txt and robots meta tags", + "homepage": "https://github.com/spatie/robots-txt", + "keywords": [ + "robots-txt", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/robots-txt/issues", + "source": "https://github.com/spatie/robots-txt/tree/2.5.1" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-07-01T07:07:44+00:00" }, { - "name": "spatie/robots-txt", - "version": "2.5.1", + "name": "spatie/shiki-php", + "version": "2.3.2", "source": { "type": "git", - "url": "https://github.com/spatie/robots-txt.git", - "reference": "ef85dfaa48372c0a7fdfb144592f95de1a2e9b79" + "url": "https://github.com/spatie/shiki-php.git", + "reference": "a2e78a9ff8a1290b25d550be8fbf8285c13175c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/robots-txt/zipball/ef85dfaa48372c0a7fdfb144592f95de1a2e9b79", - "reference": "ef85dfaa48372c0a7fdfb144592f95de1a2e9b79", + "url": "https://api.github.com/repos/spatie/shiki-php/zipball/a2e78a9ff8a1290b25d550be8fbf8285c13175c5", + "reference": "a2e78a9ff8a1290b25d550be8fbf8285c13175c5", "shasum": "" }, "require": { - "php": "^8.1" + "ext-json": "*", + "php": "^8.0", + "symfony/process": "^5.4|^6.4|^7.1" }, "require-dev": { - "phpunit/phpunit": "^11.5.2" + "friendsofphp/php-cs-fixer": "^v3.0", + "pestphp/pest": "^1.8", + "phpunit/phpunit": "^9.5", + "spatie/pest-plugin-snapshots": "^1.1", + "spatie/ray": "^1.10" }, "type": "library", "autoload": { "psr-4": { - "Spatie\\Robots\\": "src" + "Spatie\\ShikiPhp\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -7239,33 +8880,32 @@ ], "authors": [ { - "name": "Brent Roose", - "email": "brent@spatie.be", - "homepage": "https://spatie.be", + "name": "Rias Van der Veken", + "email": "rias@spatie.be", + "role": "Developer" + }, + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", "role": "Developer" } ], - "description": "Determine if a page may be crawled from robots.txt and robots meta tags", - "homepage": "https://github.com/spatie/robots-txt", + "description": "Highlight code using Shiki in PHP", + "homepage": "https://github.com/spatie/shiki-php", "keywords": [ - "robots-txt", + "shiki", "spatie" ], "support": { - "issues": "https://github.com/spatie/robots-txt/issues", - "source": "https://github.com/spatie/robots-txt/tree/2.5.1" + "source": "https://github.com/spatie/shiki-php/tree/2.3.2" }, "funding": [ - { - "url": "https://spatie.be/open-source/support-us", - "type": "custom" - }, { "url": "https://github.com/spatie", "type": "github" } ], - "time": "2025-07-01T07:07:44+00:00" + "time": "2025-02-21T14:16:57+00:00" }, { "name": "spatie/temporary-directory", @@ -7634,16 +9274,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v7.3.1", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "8b2ee2e06ab99fa5f067b6699296d4e35c156bb9" + "reference": "efa076ea0eeff504383ff0dcf827ea5ce15690ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/8b2ee2e06ab99fa5f067b6699296d4e35c156bb9", - "reference": "8b2ee2e06ab99fa5f067b6699296d4e35c156bb9", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/efa076ea0eeff504383ff0dcf827ea5ce15690ba", + "reference": "efa076ea0eeff504383ff0dcf827ea5ce15690ba", "shasum": "" }, "require": { @@ -7681,7 +9321,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v7.3.1" + "source": "https://github.com/symfony/dom-crawler/tree/v7.3.3" }, "funding": [ { @@ -7692,12 +9332,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-15T10:07:06+00:00" + "time": "2025-08-06T20:13:54+00:00" }, { "name": "symfony/error-handler", @@ -8008,6 +9652,79 @@ ], "time": "2025-07-15T13:41:35+00:00" }, + { + "name": "symfony/html-sanitizer", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/html-sanitizer.git", + "reference": "8740fc48979f649dee8b8fc51a2698e5c190bf12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/8740fc48979f649dee8b8fc51a2698e5c190bf12", + "reference": "8740fc48979f649dee8b8fc51a2698e5c190bf12", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "league/uri": "^6.5|^7.0", + "masterminds/html5": "^2.7.2", + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HtmlSanitizer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Titouan Galopin", + "email": "galopintitouan@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to sanitize untrusted HTML input for safe insertion into a document's DOM.", + "homepage": "https://symfony.com", + "keywords": [ + "Purifier", + "html", + "sanitizer" + ], + "support": { + "source": "https://github.com/symfony/html-sanitizer/tree/v7.3.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-08-12T10:34:03+00:00" + }, { "name": "symfony/http-client", "version": "v7.3.3", @@ -10169,6 +11886,75 @@ }, "time": "2024-12-21T16:25:41+00:00" }, + { + "name": "ueberdosis/tiptap-php", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/ueberdosis/tiptap-php.git", + "reference": "458194ad0f8b0cf616fecdf451a84f9a6c1f3056" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ueberdosis/tiptap-php/zipball/458194ad0f8b0cf616fecdf451a84f9a6c1f3056", + "reference": "458194ad0f8b0cf616fecdf451a84f9a6c1f3056", + "shasum": "" + }, + "require": { + "php": "^8.0", + "scrivo/highlight.php": "^9.18", + "spatie/shiki-php": "^2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.5", + "pestphp/pest": "^1.21", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Tiptap\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Hans Pagel", + "email": "humans@tiptap.dev", + "role": "Developer" + } + ], + "description": "A PHP package to work with Tiptap output", + "homepage": "https://github.com/ueberdosis/tiptap-php", + "keywords": [ + "prosemirror", + "tiptap", + "ueberdosis" + ], + "support": { + "issues": "https://github.com/ueberdosis/tiptap-php/issues", + "source": "https://github.com/ueberdosis/tiptap-php/tree/2.0.0" + }, + "funding": [ + { + "url": "https://tiptap.dev/pricing", + "type": "custom" + }, + { + "url": "https://github.com/ueberdosis", + "type": "github" + }, + { + "url": "https://opencollective.com/tiptap", + "type": "open_collective" + } + ], + "time": "2025-06-26T14:11:46+00:00" + }, { "name": "vlucas/phpdotenv", "version": "v5.6.2", @@ -10474,16 +12260,16 @@ }, { "name": "brianium/paratest", - "version": "v7.8.3", + "version": "v7.8.4", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "a585c346ddf1bec22e51e20b5387607905604a71" + "reference": "130a9bf0e269ee5f5b320108f794ad03e275cad4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/a585c346ddf1bec22e51e20b5387607905604a71", - "reference": "a585c346ddf1bec22e51e20b5387607905604a71", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/130a9bf0e269ee5f5b320108f794ad03e275cad4", + "reference": "130a9bf0e269ee5f5b320108f794ad03e275cad4", "shasum": "" }, "require": { @@ -10492,26 +12278,26 @@ "ext-reflection": "*", "ext-simplexml": "*", "fidry/cpu-core-counter": "^1.2.0", - "jean85/pretty-package-versions": "^2.1.0", + "jean85/pretty-package-versions": "^2.1.1", "php": "~8.2.0 || ~8.3.0 || ~8.4.0", - "phpunit/php-code-coverage": "^11.0.9 || ^12.0.4", - "phpunit/php-file-iterator": "^5.1.0 || ^6", - "phpunit/php-timer": "^7.0.1 || ^8", - "phpunit/phpunit": "^11.5.11 || ^12.0.6", - "sebastian/environment": "^7.2.0 || ^8", - "symfony/console": "^6.4.17 || ^7.2.1", - "symfony/process": "^6.4.19 || ^7.2.4" + "phpunit/php-code-coverage": "^11.0.10", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-timer": "^7.0.1", + "phpunit/phpunit": "^11.5.24", + "sebastian/environment": "^7.2.1", + "symfony/console": "^6.4.22 || ^7.3.0", + "symfony/process": "^6.4.20 || ^7.3.0" }, "require-dev": { "doctrine/coding-standard": "^12.0.0", "ext-pcov": "*", "ext-posix": "*", - "phpstan/phpstan": "^2.1.6", - "phpstan/phpstan-deprecation-rules": "^2.0.1", - "phpstan/phpstan-phpunit": "^2.0.4", - "phpstan/phpstan-strict-rules": "^2.0.3", - "squizlabs/php_codesniffer": "^3.11.3", - "symfony/filesystem": "^6.4.13 || ^7.2.0" + "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpstan/phpstan-phpunit": "^2.0.6", + "phpstan/phpstan-strict-rules": "^2.0.4", + "squizlabs/php_codesniffer": "^3.13.2", + "symfony/filesystem": "^6.4.13 || ^7.3.0" }, "bin": [ "bin/paratest", @@ -10551,7 +12337,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.8.3" + "source": "https://github.com/paratestphp/paratest/tree/v7.8.4" }, "funding": [ { @@ -10563,7 +12349,7 @@ "type": "paypal" } ], - "time": "2025-03-05T08:29:11+00:00" + "time": "2025-06-23T06:07:21+00:00" }, { "name": "doctrine/deprecations", @@ -10678,16 +12464,16 @@ }, { "name": "fidry/cpu-core-counter", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "8520451a140d3f46ac33042715115e290cf5785f" + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", - "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", "shasum": "" }, "require": { @@ -10697,10 +12483,10 @@ "fidry/makefile": "^0.2.0", "fidry/php-cs-fixer-config": "^1.1.2", "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^1.9.2", - "phpstan/phpstan-deprecation-rules": "^1.0.0", - "phpstan/phpstan-phpunit": "^1.2.2", - "phpstan/phpstan-strict-rules": "^1.4.4", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^8.5.31 || ^9.5.26", "webmozarts/strict-phpunit": "^7.5" }, @@ -10727,7 +12513,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" }, "funding": [ { @@ -10735,20 +12521,20 @@ "type": "github" } ], - "time": "2024-08-06T10:04:20+00:00" + "time": "2025-08-14T07:29:31+00:00" }, { "name": "filp/whoops", - "version": "2.18.3", + "version": "2.18.4", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "59a123a3d459c5a23055802237cb317f609867e5" + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/59a123a3d459c5a23055802237cb317f609867e5", - "reference": "59a123a3d459c5a23055802237cb317f609867e5", + "url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d", "shasum": "" }, "require": { @@ -10798,7 +12584,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.18.3" + "source": "https://github.com/filp/whoops/tree/2.18.4" }, "funding": [ { @@ -10806,7 +12592,7 @@ "type": "github" } ], - "time": "2025-06-16T00:02:10+00:00" + "time": "2025-08-08T12:00:00+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -10944,16 +12730,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.1", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -10992,7 +12778,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -11000,7 +12786,7 @@ "type": "tidelift" } ], - "time": "2025-04-29T12:36:36+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nunomaduro/collision", @@ -11103,38 +12889,38 @@ }, { "name": "pestphp/pest", - "version": "v3.8.2", + "version": "v3.8.4", "source": { "type": "git", "url": "https://github.com/pestphp/pest.git", - "reference": "c6244a8712968dbac88eb998e7ff3b5caa556b0d" + "reference": "72cf695554420e21858cda831d5db193db102574" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest/zipball/c6244a8712968dbac88eb998e7ff3b5caa556b0d", - "reference": "c6244a8712968dbac88eb998e7ff3b5caa556b0d", + "url": "https://api.github.com/repos/pestphp/pest/zipball/72cf695554420e21858cda831d5db193db102574", + "reference": "72cf695554420e21858cda831d5db193db102574", "shasum": "" }, "require": { - "brianium/paratest": "^7.8.3", - "nunomaduro/collision": "^8.8.0", - "nunomaduro/termwind": "^2.3.0", + "brianium/paratest": "^7.8.4", + "nunomaduro/collision": "^8.8.2", + "nunomaduro/termwind": "^2.3.1", "pestphp/pest-plugin": "^3.0.0", - "pestphp/pest-plugin-arch": "^3.1.0", + "pestphp/pest-plugin-arch": "^3.1.1", "pestphp/pest-plugin-mutate": "^3.0.5", "php": "^8.2.0", - "phpunit/phpunit": "^11.5.15" + "phpunit/phpunit": "^11.5.33" }, "conflict": { "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">11.5.15", + "phpunit/phpunit": ">11.5.33", "sebastian/exporter": "<6.0.0", "webmozart/assert": "<1.11.0" }, "require-dev": { "pestphp/pest-dev-tools": "^3.4.0", - "pestphp/pest-plugin-type-coverage": "^3.5.0", - "symfony/process": "^7.2.5" + "pestphp/pest-plugin-type-coverage": "^3.6.1", + "symfony/process": "^7.3.0" }, "bin": [ "bin/pest" @@ -11199,7 +12985,7 @@ ], "support": { "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v3.8.2" + "source": "https://github.com/pestphp/pest/tree/v3.8.4" }, "funding": [ { @@ -11211,7 +12997,7 @@ "type": "github" } ], - "time": "2025-04-17T10:53:02+00:00" + "time": "2025-08-20T19:12:42+00:00" }, { "name": "pestphp/pest-plugin", @@ -11427,6 +13213,72 @@ ], "time": "2025-04-21T07:40:53+00:00" }, + { + "name": "pestphp/pest-plugin-livewire", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-livewire.git", + "reference": "e2f2edb0a7d414d6837d87908a0e148256d3bf89" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-livewire/zipball/e2f2edb0a7d414d6837d87908a0e148256d3bf89", + "reference": "e2f2edb0a7d414d6837d87908a0e148256d3bf89", + "shasum": "" + }, + "require": { + "livewire/livewire": "^3.5.6", + "pestphp/pest": "^3.0.0", + "php": "^8.1" + }, + "require-dev": { + "orchestra/testbench": "^9.4.0", + "pestphp/pest-dev-tools": "^3.0.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "Pest\\Livewire\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Pest Livewire Plugin", + "keywords": [ + "framework", + "livewire", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-livewire/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2024-09-09T00:05:59+00:00" + }, { "name": "pestphp/pest-plugin-mutate", "version": "v3.0.5", @@ -11914,16 +13766,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "11.0.9", + "version": "11.0.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7" + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/14d63fbcca18457e49c6f8bebaa91a87e8e188d7", - "reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", "shasum": "" }, "require": { @@ -11980,15 +13832,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.9" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.11" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" } ], - "time": "2025-02-25T13:26:39+00:00" + "time": "2025-08-27T14:37:49+00:00" }, { "name": "phpunit/php-file-iterator", @@ -12237,16 +14101,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.15", + "version": "11.5.33", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "4b6a4ee654e5e0c5e1f17e2f83c0f4c91dee1f9c" + "reference": "5965e9ff57546cb9137c0ff6aa78cb7442b05cf6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4b6a4ee654e5e0c5e1f17e2f83c0f4c91dee1f9c", - "reference": "4b6a4ee654e5e0c5e1f17e2f83c0f4c91dee1f9c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5965e9ff57546cb9137c0ff6aa78cb7442b05cf6", + "reference": "5965e9ff57546cb9137c0ff6aa78cb7442b05cf6", "shasum": "" }, "require": { @@ -12256,24 +14120,24 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.0", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.9", + "phpunit/php-code-coverage": "^11.0.10", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", "sebastian/code-unit": "^3.0.3", - "sebastian/comparator": "^6.3.1", + "sebastian/comparator": "^6.3.2", "sebastian/diff": "^6.0.2", - "sebastian/environment": "^7.2.0", + "sebastian/environment": "^7.2.1", "sebastian/exporter": "^6.3.0", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", - "sebastian/type": "^5.1.2", + "sebastian/type": "^5.1.3", "sebastian/version": "^5.0.2", "staabm/side-effects-detector": "^1.0.5" }, @@ -12318,7 +14182,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.15" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.33" }, "funding": [ { @@ -12329,12 +14193,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2025-03-23T16:02:11+00:00" + "time": "2025-08-16T05:19:02+00:00" }, { "name": "sebastian/cli-parser", @@ -12508,16 +14380,16 @@ }, { "name": "sebastian/comparator", - "version": "6.3.1", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959" + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/24b8fbc2c8e201bb1308e7b05148d6ab393b6959", - "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8", + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8", "shasum": "" }, "require": { @@ -12576,15 +14448,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.2" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2025-03-07T06:57:01+00:00" + "time": "2025-08-10T08:07:46+00:00" }, { "name": "sebastian/complexity", @@ -12713,23 +14597,23 @@ }, { "name": "sebastian/environment", - "version": "7.2.0", + "version": "7.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5" + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", - "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "suggest": { "ext-posix": "*" @@ -12765,15 +14649,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0" + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" } ], - "time": "2024-07-03T04:54:44+00:00" + "time": "2025-05-21T11:55:47+00:00" }, { "name": "sebastian/exporter", @@ -13089,23 +14985,23 @@ }, { "name": "sebastian/recursion-context", - "version": "6.0.2", + "version": "6.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", - "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { @@ -13141,28 +15037,40 @@ "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2024-07-03T05:10:34+00:00" + "time": "2025-08-13T04:42:22+00:00" }, { "name": "sebastian/type", - "version": "5.1.2", + "version": "5.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e" + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", - "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", "shasum": "" }, "require": { @@ -13198,15 +15106,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/5.1.2" + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" } ], - "time": "2025-03-18T13:35:50+00:00" + "time": "2025-08-09T06:55:48+00:00" }, { "name": "sebastian/version", @@ -13496,7 +15416,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "8.3.*" + "php": "^8.3 || ^8.4" }, "platform-dev": {}, "plugin-api-version": "2.6.0" diff --git a/resources/views/admin/articles.blade.php b/resources/views/admin/articles.blade.php deleted file mode 100644 index 407b12438..000000000 --- a/resources/views/admin/articles.blade.php +++ /dev/null @@ -1,84 +0,0 @@ -@title('Articles') - -@extends('layouts.default') - -@section('content') -