Server-driven UI for Laravel.

You describe what you want on the backend. Lodestone draws it with React and shadcn.

Built with Laravel Inertia React shadcn Tailwind CSS
support.acme.com
A tickets table with search, a status filter, status and priority badges and relative times, in a panel with six sidebar pages.
app/Admin/Pages/TicketsPage.php
class TicketsPage extends Page
{
    public function schema(): array
    {
        return [
            Stats::make([
                Stat::make('Open', fn () => Ticket::open()->count())->chart(fn () => Ticket::open()->perDay(7)),
                Stat::make('Pending', fn () => Ticket::pending()->count())->warning(),
                Stat::make('Resolved this week', fn () => Ticket::resolvedThisWeek()->count())->success(),
            ]),

            Table::make('tickets')
                ->query(Ticket::query()->with('customer')->latest())
                ->columns([
                    TextColumn::make('subject')->searchable()->description('customer.name'),
                    BadgeColumn::make('status')->colors(['info' => 'open', 'warning' => 'pending', 'success' => 'resolved']),
                    TextColumn::make('created_at')->label('Opened')->relative()->sortable(),
                ])
                ->rowButtons([
                    Button::make('resolve')->icon('check')
                        ->visible(fn (Ticket $ticket) => ! $ticket->isResolved())
                        ->click(function (Ticket $ticket, Ui $ui) {
                            $ticket->resolve();
                            $ui->toast("Resolved #{$ticket->id}.");
                        }),

                    Button::make('refund')->label('Issue refund')->icon('receipt')
                        ->form(RefundForm::make()->submit(function (Ticket $ticket, array $data, Ui $ui) {
                            $ticket->customer->refund($data['amount'], $data['reason']);
                            $ui->toast("Refunded \${$data['amount']}.");
                        })),
                ])
                ->recordModal(TicketPage::class),
        ];
    }
}

When would you use Lodestone?


Install.

TODO