Blog

Manage Laravel Comments using Filament Admin

There is a great YouTube video about how to manage Laravel Comments, a package by Spatie, using Filament Admin.

I could not find the source code anywhere else, that's why I wrote the code following the video. If anybody else may need the code, here it is:

Create a Comment filament resource:

php artisan filament:resource Comment --view --simple 

Create a User filament resource

php artisan filament:resource User --generate

Create a Comments widget:

php artisan filament:widget Comments

Content of the CommentResource.php:

<?php

namespace App\Filament\Resources;

use App\Filament\Resources\CommentResource\Pages;
use App\Filament\Resources\CommentResource\RelationManagers;
use App\Models\User;
use Filament\Facades\Filament;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\MarkdownEditor;
use Filament\Resources\Form;
use Filament\Resources\Resource;
use Filament\Resources\Table;
use Filament\Tables;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\HtmlString;
use Spatie\Comments\Models\Comment;
use Spatie\Comments\Models\Concerns\HasComments;
use Spatie\Comments\Models\Concerns\Interfaces\CanComment;

class CommentResource extends Resource
{
    protected static ?string $model = Comment::class;

    protected static ?string $navigationIcon = 'heroicon-o-collection';

    public static function form(Form $form): Form
    {
        return $form
            ->schema([
                MarkdownEditor::make('original_text')
                    ->label('Comment')
                    ->columnSpanFull()
            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                Tables\Columns\TextColumn::make('commentable.name')->getStateUsing(
                    function (Comment $record): string {
                        /** @var HasComments $commentable */
                        $commentable = $record->topLevel()->commentable;
                        return $commentable?->commentableName() ?? 'Deleted';
                    }
                )->url(fn(Comment $record): string => $record->commentUrl())
                    ->openUrlInNewTab(),
//                    ->searchable([static::getCommentableTitleAttribute()]),
                Tables\Columns\TextColumn::make('commentator.name')->getStateUsing(
                    function (Comment $record): string {
                        /** @var CanComment $commentator */
                        $commentator = $record->commentator;
                        return $commentator?->commentatorProperties()?->name ?? 'Guest';
                    }
                )->url(function (Comment $record): ?string {
                    $resource = match ($record->commentator::class) {
                        User::class => UserResource::class,
                    };

                    if (!$resource) {
                        return null;
                    }

                    return $resource::getUrl('edit', ['record' => $record->commentator]);
                }),
                Tables\Columns\BadgeColumn::make('status')->getStateUsing(function (Comment $record): string {
                    return $record->isApproved() ? 'Approved' : 'Pending';
                })->colors([
                    'success' => 'Approved',
                    'warning' => 'Pending'
                ]),
                Tables\Columns\TextColumn::make('created_at')->dateTime()->sortable()
            ])->filters([
                Tables\Filters\TernaryFilter::make('approved_at')
                    ->label('Approval status')
                    ->trueLabel('Approved')
                    ->falseLabel('Pending')
                    ->placeholder('All')
                    ->nullable(),
                Tables\Filters\Filter::make('created_at')->form([
                    DatePicker::make('created_from'),
                    DatePicker::make('created_until'),
                ])->query(function (Builder $query, array $data) {
                    return $query->when(
                        $data['created_from'],
                        fn(Builder $query) => $query->whereDate('created_at', '>=', $data['created_from'])
                    )->when(
                        $data['created_until'],
                        fn(Builder $query) => $query->whereDate('created_at', '<=', $data['created_until'])
                    );
                })
            ])
            ->actions([
                Tables\Actions\Action::make('approve')->action(function (Comment $record) {
                    $record->approve();
                    Filament::notify('success', 'Approved');
                })->requiresConfirmation()->hidden(fn(Comment $record): bool => $record->isApproved())->color('success')
                    ->icon('heroicon-s-check'),
                Tables\Actions\Action::make('reject')->action(function (Comment $record) {
                    $record->reject();
                    Filament::notify('success', 'Rejected');
                })->requiresConfirmation()->visible(fn(Comment $record): bool => $record->isApproved())->color('danger')
                    ->icon('heroicon-s-x'),
                Tables\Actions\ViewAction::make()->modalContent(fn(Comment $record): HtmlString => new HtmlString($record->text)),
                Tables\Actions\EditAction::make()->form([]),
            ])
            ->bulkActions([
                Tables\Actions\BulkAction::make('approve')->action(function (Collection $records) {
                    $records->each->approve();
                    Filament::notify('success', 'Approved');
                })->requiresConfirmation()->color('success')
                    ->icon('heroicon-s-check'),
                Tables\Actions\BulkAction::make('reject')->action(function (Collection $records) {
                    $records->each->reject();
                    Filament::notify('success', 'Rejected');
                })->requiresConfirmation()->color('danger')
                    ->icon('heroicon-s-x'),
            ]);
    }

    public static function canEdit(Model $record): bool
    {
        return true;
    }

    public static function getPages(): array
    {
        return [
            'index' => Pages\ManageComments::route('/'),
        ];
    }
}

Add this to PostResource.php:

public static function getWidgets(): array
{
    return [
        PostResource\Widgets\Comments::class
    ];
}

Content of PostResource/Widgets/Comments.php:

<?php

namespace App\Filament\Resources\PostResource\Widgets;

use Filament\Widgets\Widget;
use Illuminate\Database\Eloquent\Model;

class Comments extends Widget
{
    protected static string $view = 'filament.resources.post-resource.widgets.comments';

    public Model $record;

    protected int|string|array $columnSpan = 'full';
}

Add this code to PostResource/Pages/EditPost.php:

protected function getFooterWidgets(): array
{
    return [
        PostResource\Widgets\Comments::class
    ];
}

Content of comments.blade.php:

<x-filament::widget>
    <x-filament::card>
        <livewire:comments :model="$record" />
    </x-filament::card>
</x-filament::widget>

Add this to boot method of AppServiceProvider.php:

Filament::registerRenderHook(
    'styles.start',
    fn(): string => \Blade::render('<x-comments::styles />')
);

Filament::registerRenderHook(
    'scripts.start',
    fn(): string => \Blade::render('<x-comments::scripts />')
);

That's it, you should now be able to manage your Laravel Comments using Filament Admin.