Chore: Upgrade to Laravel 12 + remove Mary and Jetstream dependencies (#141)

* docs: remove requirement for setting APP_KEY manually

* optimize date picker

* clean up modals

* spot light working

* reorganization

* add lazy load

* wip

* remove filament

* styling
This commit is contained in:
hackerESQ
2025-09-26 17:41:28 -05:00
committed by GitHub
parent 910d426ad4
commit e6f38d9481
146 changed files with 5443 additions and 3909 deletions
-21
View File
@@ -1,21 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Actions\Jetstream;
use App\Models\User;
use Laravel\Jetstream\Contracts\DeletesUsers;
class DeleteUser implements DeletesUsers
{
/**
* Delete the given user.
*/
public function delete(User $user): void
{
$user->deleteProfilePhoto();
$user->tokens->each->delete();
$user->delete();
}
}
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
class ApiTokenController extends Controller
{
/**
* Show the user API token screen.
*
* @return \Illuminate\View\View
*/
public function index(Request $request)
{
return view('api.index', [
'request' => $request,
'user' => $request->user(),
]);
}
}
@@ -124,7 +124,7 @@ class ConnectedAccountController extends Controller
'title' => __('Your :provider account has been connected.', ['provider' => config("services.{$connected_account->provider}.name")]),
'description' => null,
'css' => 'alert-success',
'icon' => Blade::render("<x-mary-icon class='w-7 h-7' name='o-check-circle' />"),
'icon' => Blade::render("<x-ui.icon class='w-7 h-7' name='o-check-circle' />"),
'position' => 'toast-top toast-end',
'timeout' => '5000',
],
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Traits\HasLocalizedMarkdown;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Str;
class PrivacyPolicyController extends Controller
{
use HasLocalizedMarkdown;
/**
* Show the privacy policy for the application.
*
* @return \Illuminate\View\View
*/
public function show(Request $request)
{
$policyFile = $this->localizedMarkdownPath('policy.md');
return view('policy', [
'policy' => Str::markdown(file_get_contents($policyFile)),
]);
}
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Traits\HasLocalizedMarkdown;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Str;
class TermsOfServiceController extends Controller
{
use HasLocalizedMarkdown;
/**
* Show the terms of service for the application.
*
* @return \Illuminate\View\View
*/
public function show(Request $request)
{
$termsFile = $this->localizedMarkdownPath('terms.md');
return view('terms', [
'terms' => Str::markdown(file_get_contents($termsFile)),
]);
}
}
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
class UserProfileController extends Controller
{
/**
* Show the user profile screen.
*
* @return \Illuminate\View\View
*/
public function show(Request $request)
{
return view('profile.show', [
'request' => $request,
'user' => $request->user(),
]);
}
}
+169
View File
@@ -0,0 +1,169 @@
<?php
namespace App\Livewire\Datatables;
use App\Models\Holding;
use Illuminate\Support\Number;
use Illuminate\Database\Eloquent\Builder;
use Rappasoft\LaravelLivewireTables\Views\Column;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
class HoldingsTable extends DataTableComponent
{
public $portfolio;
public array $hiddenColumns = [];
public function mount ($portfolio): void
{
//
}
public function builder(): Builder
{
return Holding::query()
->portfolio($this->portfolio->id)
->with(['market_data'])
->withCount(['transactions as num_transactions' => function ($query) {
return $query->whereRaw('transactions.symbol = holdings.symbol');
}])
->withPerformance();
}
public function configure(): void
{
$this->hiddenColumns = ['name', 'average_cost_basis', 'market_value', 'fifty_two_week_low', 'fifty_two_week_high'];
$this->setTableWrapperAttributes([
'default' => false,
'default-styling' => false,
'default-colors' => false,
'class' => 'overflow-scroll'
]);
$this->setTableAttributes([
'default' => false,
'default-styling' => false,
'default-colors' => false,
'class' => 'table',
]);
$this->setTheadAttributes([
'default' => false,
'default-styling' => true,
'default-colors' => false,
]);
$this->setThAttributes(function(Column $column) {
$attributes = [
'default' => false,
'default-styling' => false,
'default-colors' => false,
'class' => 'text-xs font-medium whitespace-nowrap uppercase tracking-wider text-nowrap'
];
if (in_array($column->getField(), $this->hiddenColumns)) {
$attributes['class'] = $attributes['class'] . ' hidden md:table-cell';
}
return $attributes;
});
$this->setThSortButtonAttributes(fn() => [
'default' => false,
'default-styling' => true,
'default-colors' => false,
'class' => 'cursor-pointer'
]);
$this->setTbodyAttributes([
'default' => false,
'default-styling' => true,
'default-colors' => false,
]);
$this->setTrAttributes(fn() => [
'default' => false,
'default-styling' => true,
'default-colors' => false,
'class' => 'cursor-pointer hover:bg-neutral/25'
]);
$this->setTdAttributes(function(Column $column) {
$attributes = [
'default' => false,
'default-styling' => false,
'default-colors' => false,
'class' => 'text-nowrap'
];
if (in_array($column->getField(), $this->hiddenColumns)) {
$attributes['class'] = $attributes['class'] . ' hidden md:table-cell';
}
return $attributes;
});
$this->setDefaultSort('symbol', 'asc');
$this->setToolsDisabled();
$this->setFooterDisabled();
$this->setPaginationDisabled();
$this->setDisplayPaginationDetailsDisabled();
$this->setPrimaryKey('id');
$this->setTableRowUrl(function($row) {
return route('holding.show', ['portfolio' => $row->portfolio_id, 'symbol' => $row->symbol]);
})->setTableRowUrlTarget(function($row) {
return 'navigate';
});
}
public function columns(): array
{
return [
Column::make(__('Symbol'), 'symbol')
->sortable(),
Column::make(__('Name'), 'market_data.name')
->sortable(),
Column::make(__('Quantity'), 'quantity')
->sortable(),
Column::make(__('Average Cost Basis'), 'average_cost_basis')
->sortable()
->format(fn($value, $row) => Number::currency($value ?? 0, $row->market_data->currency) ),
Column::make(__('Total Cost Basis'), 'total_cost_basis')
->sortable()
->format(fn($value, $row) => Number::currency($value ?? 0, $row->market_data->currency) ),
Column::make(__('Market Value'), 'market_data.market_value')
->sortable()
->format(fn($value, $row) => Number::currency($value ?? 0, $row->market_data->currency) ),
Column::make(__('Total Market Value'))
->sortable(fn (Builder $query, string $direction) => $query->orderBy('total_market_value', $direction))
->label(fn ($row) => Number::currency($row->total_market_value ?? 0, $row->market_data->currency)),
Column::make(__('Market Gain/Loss'))
->html()
->label(fn($row) => Number::currency($row->market_gain_dollars ?? 0, $row->market_data->currency) . view('components.ui.gain-loss-arrow-badge', [
'costBasis' => $row->average_cost_basis,
'marketValue' => $row->market_data->market_value,
'small' => true,
]))
->sortable(fn (Builder $query, string $direction) => $query->orderBy('market_gain_dollars', $direction)),
Column::make(__('Realized Gain/Loss'), 'realized_gain_dollars')
->sortable()
->format(fn($value, $row) => Number::currency($value ?? 0, $row->market_data->currency) )
->format(fn($value, $row) => Number::currency($value ?? 0, $row->market_data->currency) ),
Column::make(__('Dividends Earned'), 'dividends_earned')
->sortable()
->format(fn($value, $row) => Number::currency($value ?? 0, $row->market_data->currency) ),
Column::make(__('52 week low'), 'market_data.fifty_two_week_low')
->sortable()
->format(fn($value, $row) => Number::currency($value ?? 0, $row->market_data->currency) ),
Column::make(__('52 week high'), 'market_data.fifty_two_week_high')
->sortable()
->format(fn($value, $row) => Number::currency($value ?? 0, $row->market_data->currency) ),
Column::make(__('Number of Transactions'))
->sortable(fn (Builder $query, string $direction) => $query->orderBy('num_transactions', $direction))
->label(fn ($row) => $row->num_transactions),
Column::make(__('Last Refreshed'), 'market_data.updated_at')
->sortable()
->format(fn($value) => \Carbon\Carbon::parse($value)->diffForHumans() )
];
}
}
@@ -0,0 +1,157 @@
<?php
namespace App\Livewire\Datatables;
use App\Models\Transaction;
use Illuminate\Support\Number;
use Illuminate\Database\Eloquent\Builder;
use Rappasoft\LaravelLivewireTables\Views\Column;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
class TransactionsTable extends DataTableComponent
{
public array $hiddenColumns = [];
public function mount (): void
{
//
}
public function builder(): Builder
{
return Transaction::query()
->with(['portfolio', 'market_data'])
->myTransactions()
->addSelect(['portfolio_id', 'transaction_type', 'split'])
->selectRaw('
CASE
WHEN transaction_type = \'SELL\'
THEN COALESCE(transactions.sale_price - transactions.cost_basis, 0)
ELSE COALESCE(market_data.market_value - transactions.cost_basis, 0)
END AS gain_dollars');
}
public function configure(): void
{
$this->hiddenColumns = ['name', 'cost_basis', 'gain_dollars'];
$this->setTableWrapperAttributes([
'default' => false,
'default-styling' => false,
'default-colors' => false,
'class' => 'overflow-scroll'
]);
$this->setTableAttributes([
'default' => false,
'default-styling' => false,
'default-colors' => false,
'class' => 'table',
]);
$this->setTheadAttributes([
'default' => false,
'default-styling' => true,
'default-colors' => false,
]);
$this->setThAttributes(function(Column $column) {
$attributes = [
'default' => false,
'default-styling' => false,
'default-colors' => false,
'class' => 'text-xs font-medium whitespace-nowrap uppercase tracking-wider text-nowrap'
];
if (in_array($column->getField(), $this->hiddenColumns)) {
$attributes['class'] = $attributes['class'] . ' hidden md:table-cell';
}
return $attributes;
});
$this->setThSortButtonAttributes(fn() => [
'default' => false,
'default-styling' => true,
'default-colors' => false,
'class' => 'cursor-pointer'
]);
$this->setTbodyAttributes([
'default' => false,
'default-styling' => true,
'default-colors' => false,
]);
$this->setTrAttributes(fn() => [
'default' => false,
'default-styling' => true,
'default-colors' => false,
'class' => 'cursor-pointer hover:bg-neutral/25'
]);
$this->setTdAttributes(function(Column $column) {
$attributes = [
'default' => false,
'default-styling' => false,
'default-colors' => false,
'class' => 'text-nowrap'
];
if (in_array($column->getField(), $this->hiddenColumns)) {
$attributes['class'] = $attributes['class'] . ' hidden md:table-cell';
}
return $attributes;
});
$this->setDefaultSort('date', 'desc');
$this->setPerPageAccepted([10, 15, 20]);
$this->setPerPage(15);
$this->setSearchDisabled();
$this->setColumnSelectDisabled();
$this->setPerPageVisibilityDisabled();
$this->setFooterDisabled();
$this->setPrimaryKey('id');
$this->setTableRowUrl(function($row) {
return route('holding.show', ['portfolio' => $row->portfolio_id, 'symbol' => $row->symbol]);
})->setTableRowUrlTarget(function($row) {
return 'navigate';
});
}
public function columns(): array
{
return [
Column::make(__('Date'), 'date')
->sortable()
->format(fn($value) => \Carbon\Carbon::parse($value)->format('M d, Y') ),
Column::make(__('Portfolio'), 'portfolio.title')
->sortable(),
Column::make(__('Symbol'), 'symbol')
->sortable(),
Column::make(__('Name'), 'market_data.name')
->sortable(),
Column::make(__('Type'), 'transaction_type')
->label(fn($row) => view('components.ui.badge', [
'value' => $row->split ? 'SPLIT'
: ($row->reinvested_dividend
? 'REINVEST'
: $row->transaction_type),
'class' => ($row->transaction_type == 'BUY'
? 'badge-success'
: 'badge-error') . ' badge-sm mr-3',
]))
->sortable(fn (Builder $query, string $direction) => $query->orderBy('transaction_type', $direction)),
Column::make(__('Quantity'), 'quantity')
->sortable(),
Column::make(__('Cost Basis'), 'cost_basis')
->sortable(fn (Builder $query, string $direction) => $query->orderBy('cost_basis', $direction))
->label(fn ($row) => Number::currency($row->cost_basis ?? 0, $row->market_data->currency)),
Column::make(__('Gain/Loss'), 'gain_dollars')
->sortable(fn (Builder $query, string $direction) => $query->orderBy('gain_dollars', $direction))
->label(fn ($row) => Number::currency($row->gain_dollars ?? 0, $row->market_data->currency)),
];
}
}
+1 -1
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Models;
use App\Traits\HasConnectedAccounts;
use App\Traits\HasProfilePhoto;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -12,7 +13,6 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Arr;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Jetstream\HasProfilePhoto;
use Laravel\Sanctum\HasApiTokens;
use Staudenmeir\EloquentHasManyDeep\HasManyDeep;
use Staudenmeir\EloquentHasManyDeep\HasRelationships;
+1
View File
@@ -30,6 +30,7 @@ class FortifyServiceProvider extends ServiceProvider
*/
public function boot(): void
{
Fortify::viewPrefix('auth.');
Fortify::createUsersUsing(CreateNewUser::class);
Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
@@ -1,55 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Providers;
use App\Actions\Jetstream\DeleteUser;
use Illuminate\Support\ServiceProvider;
use Laravel\Jetstream\Jetstream;
class JetstreamServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
$this->configurePermissions();
Jetstream::deleteUsersUsing(DeleteUser::class);
}
/**
* Configure the permissions that are available within the application.
*/
protected function configurePermissions(): void
{
Jetstream::defaultApiTokenPermissions([
// 'portfolio:read',
// 'portfolio:write',
// 'holding:read',
// 'holding:write',
// 'transaction:read',
// 'transaction:write',
]);
Jetstream::permissions([
// 'Read Portfolios' => 'portfolio:read',
// 'Create Portfolios' => 'portfolio:write',
// 'Read Holdings' => 'holding:read',
// 'Update Holdings' => 'holding:write',
// 'Read Transactions' => 'transaction:read',
// 'Create Transactions' => 'transaction:write',
]);
}
}
+4 -2
View File
@@ -4,8 +4,8 @@ declare(strict_types=1);
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Livewire\Volt\Volt;
use Illuminate\Support\ServiceProvider;
class VoltServiceProvider extends ServiceProvider
{
@@ -21,14 +21,16 @@ class VoltServiceProvider extends ServiceProvider
* Bootstrap services.
*/
public function boot(): void
{
{
Volt::mount([
// config('livewire.view_path', resource_path('views/livewire')),
resource_path('views/components'),
resource_path('views/profile'),
resource_path('views/api'),
resource_path('views/holding'),
resource_path('views/transaction'),
resource_path('views/portfolio'),
resource_path('views/import-export'),
resource_path('views/auth'),
]);
}
+115
View File
@@ -0,0 +1,115 @@
<?php
namespace App\Traits;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
use Laravel\Fortify\Actions\ConfirmPassword;
trait ConfirmsPasswords
{
/**
* Indicates if the user's password is being confirmed.
*
* @var bool
*/
public $confirmingPassword = false;
/**
* The ID of the operation being confirmed.
*
* @var string|null
*/
public $confirmableId = null;
/**
* The user's password.
*
* @var string
*/
public $confirmablePassword = '';
/**
* Start confirming the user's password.
*
* @param string $confirmableId
* @return void
*/
public function startConfirmingPassword(string $confirmableId)
{
$this->resetErrorBag();
if ($this->passwordIsConfirmed()) {
return $this->dispatch('password-confirmed',
id: $confirmableId,
);
}
$this->confirmingPassword = true;
$this->confirmableId = $confirmableId;
$this->confirmablePassword = '';
$this->dispatch('confirming-password');
}
/**
* Stop confirming the user's password.
*
* @return void
*/
public function stopConfirmingPassword()
{
$this->confirmingPassword = false;
$this->confirmableId = null;
$this->confirmablePassword = '';
}
/**
* Confirm the user's password.
*
* @return void
*/
public function confirmPassword()
{
if (! app(ConfirmPassword::class)(app(StatefulGuard::class), Auth::user(), $this->confirmablePassword)) {
throw ValidationException::withMessages([
'confirmable_password' => [__('This password does not match our records.')],
]);
}
session(['auth.password_confirmed_at' => time()]);
$this->dispatch('password-confirmed',
id: $this->confirmableId,
);
$this->stopConfirmingPassword();
}
/**
* Ensure that the user's password has been recently confirmed.
*
* @param int|null $maximumSecondsSinceConfirmation
* @return void
*/
protected function ensurePasswordIsConfirmed($maximumSecondsSinceConfirmation = null)
{
$maximumSecondsSinceConfirmation = $maximumSecondsSinceConfirmation ?: config('auth.password_timeout', 900);
$this->passwordIsConfirmed($maximumSecondsSinceConfirmation) ? null : abort(403);
}
/**
* Determine if the user's password has been recently confirmed.
*
* @param int|null $maximumSecondsSinceConfirmation
* @return bool
*/
protected function passwordIsConfirmed($maximumSecondsSinceConfirmation = null)
{
$maximumSecondsSinceConfirmation = $maximumSecondsSinceConfirmation ?: config('auth.password_timeout', 900);
return (time() - session('auth.password_confirmed_at', 0)) < $maximumSecondsSinceConfirmation;
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace App\Traits;
use Illuminate\Support\Arr;
trait HasLocalizedMarkdown
{
public function localizedMarkdownPath($name)
{
$localName = preg_replace('#(\.md)$#i', '.'.app()->getLocale().'$1', $name);
return Arr::first([
resource_path('markdown/'.$localName),
resource_path('markdown/'.$name),
], function ($path) {
return file_exists($path);
});
}
}
+77
View File
@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace App\Traits;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
trait HasProfilePhoto
{
/**
* Update the user's profile photo.
*
* @param string $storagePath
* @return void
*/
public function updateProfilePhoto(UploadedFile $photo, $storagePath = 'profile-photos')
{
tap($this->profile_photo_path, function ($previous) use ($photo, $storagePath) {
$this->forceFill([
'profile_photo_path' => $photo->storePublicly(
$storagePath, ['disk' => 'public']
),
])->save();
if ($previous) {
Storage::disk('public')->delete($previous);
}
});
}
/**
* Delete the user's profile photo.
*
* @return void
*/
public function deleteProfilePhoto()
{
if (is_null($this->profile_photo_path)) {
return;
}
Storage::disk('public')->delete($this->profile_photo_path);
$this->forceFill([
'profile_photo_path' => null,
])->save();
}
/**
* Get the URL to the user's profile photo.
*/
protected function profilePhotoUrl(): Attribute
{
return Attribute::get(function (): string {
return $this->profile_photo_path
? Storage::disk('public')->url($this->profile_photo_path)
: $this->defaultProfilePhotoUrl();
});
}
/**
* Get the default profile photo URL if no profile photo has been uploaded.
*
* @return string
*/
protected function defaultProfilePhotoUrl()
{
$name = trim(collect(explode(' ', $this->name))->map(function ($segment) {
return mb_substr($segment, 0, 1);
})->join(' '));
return 'https://ui-avatars.com/api/?name='.urlencode($name).'&color=7F9CF5&background=EBF4FF';
}
}
+88
View File
@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace App\Traits;
use Illuminate\Support\Facades\Blade;
trait Toast
{
public function toast(
string $type,
string $title,
?string $description = null,
?string $position = null,
string $icon = 'o-information-circle',
string $css = 'alert-info',
int $timeout = 3000,
?string $redirectTo = null
) {
$toast = [
'type' => $type,
'title' => $title,
'description' => $description,
'position' => $position,
'icon' => Blade::render("<x-ui.icon class='w-7 h-7' name='".$icon."' />"),
'css' => $css,
'timeout' => $timeout,
];
$this->js('toast('.json_encode(['toast' => $toast]).')');
// session()->flash('ib.toast.title', $title);
// session()->flash('ib.toast.description', $description);
if ($redirectTo) {
return $this->redirect($redirectTo, navigate: true);
}
}
public function success(
string $title,
?string $description = null,
?string $position = null,
string $icon = 'o-check-circle',
string $css = 'alert-success',
int $timeout = 3000,
?string $redirectTo = null
) {
return $this->toast('success', $title, $description, $position, $icon, $css, $timeout, $redirectTo);
}
public function warning(
string $title,
?string $description = null,
?string $position = null,
string $icon = 'o-exclamation-triangle',
string $css = 'alert-warning',
int $timeout = 3000,
?string $redirectTo = null
) {
return $this->toast('warning', $title, $description, $position, $icon, $css, $timeout, $redirectTo);
}
public function error(
string $title,
?string $description = null,
?string $position = null,
string $icon = 'o-x-circle',
string $css = 'alert-error',
int $timeout = 3000,
?string $redirectTo = null
) {
return $this->toast('error', $title, $description, $position, $icon, $css, $timeout, $redirectTo);
}
public function info(
string $title,
?string $description = null,
?string $position = null,
string $icon = 'o-information-circle',
string $css = 'alert-info',
int $timeout = 3000,
?string $redirectTo = null
) {
return $this->toast('info', $title, $description, $position, $icon, $css, $timeout, $redirectTo);
}
}
-53
View File
@@ -1,53 +0,0 @@
<?php
declare(strict_types=1);
namespace App\View\Components;
use Illuminate\View\Component;
class AppLayout extends Component
{
/**
* Get the view / contents that represents the component.
*/
public function render()
{
return <<<'HTML'
<x-main-layout>
<x-slot:body class="min-h-screen font-sans antialiased bg-base-200/50 dark:bg-base-200" x-data>
<div>
<x-partials.nav-bar />
<x-partials.main with-nav full-width>
<x-slot:sidebar drawer="main-drawer" class="bg-base-100 lg:bg-inherit">
@livewire('partials.side-bar')
</x-slot:sidebar>
<x-slot:content>
{{ $slot }}
</x-slot:content>
</x-partials.main>
@if(session('toast'))
<script lang="text/javascript">
window.addEventListener('DOMContentLoaded', function () {
window.toast(JSON.parse(@json(session('toast'))))
});
</script>
@endif
<x-toast />
</div>
</x-slot:body>
</x-main-layout>
HTML;
}
}
-28
View File
@@ -1,28 +0,0 @@
<?php
declare(strict_types=1);
namespace App\View\Components;
use Illuminate\View\Component;
class GuestLayout extends Component
{
/**
* Get the view / contents that represents the component.
*/
public function render()
{
return <<<'HTML'
<x-main-layout>
<x-slot:body class="font-sans text-gray-900 dark:text-gray-100 antialiased">
{{ $slot }}
<x-theme-toggle class="hidden" darkTheme="business" lightTheme="corporate"/>
</x-slot:body>
</x-main-layout>
HTML;
}
}
-25
View File
@@ -1,25 +0,0 @@
<?php
declare(strict_types=1);
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
class MainLayout extends Component
{
public function __construct(
// Slots
public mixed $body = null,
) {}
/**
* Get the view / contents that represents the component.
*/
public function render(): View
{
return view('layouts.main-layout');
}
}