From 80873877c1599b446d0e385919945dd79099f9a0 Mon Sep 17 00:00:00 2001 From: SEB Fotografie - soeren Date: Fri, 1 Aug 2025 23:34:41 +0200 Subject: [PATCH] language files combined, settings fixed, "new" badge integrated --- .../AiModelResource/Pages/ListAiModels.php | 33 +++ .../Pages/ListApiProviders.php | 33 +++ app/Filament/Resources/ImageResource.php | 3 + .../ImageResource/Pages/ListImages.php | 33 +++ app/Filament/Resources/SettingResource.php | 21 +- .../SettingResource/Pages/Settings.php | 64 ----- app/Filament/Resources/StyleResource.php | 4 +- .../StyleResource/Pages/ListStyles.php | 33 +++ app/Filament/Resources/UserResource.php | 62 +++-- .../UserResource/Pages/ListUsers.php | 33 +++ app/Http/Controllers/Api/ImageController.php | 52 +++- app/Http/Controllers/HomeController.php | 15 +- app/Http/Middleware/HandleInertiaRequests.php | 11 +- app/Http/Middleware/SetLocale.php | 14 +- app/Models/Image.php | 1 + app/Models/User.php | 3 + app/Providers/AppServiceProvider.php | 5 + app/Providers/Filament/AdminPanelProvider.php | 20 +- composer.json | 1 - composer.lock | 253 +++++++++++------- ...025_07_30_143255_create_settings_table.php | 3 +- ..._01_055332_add_settings_to_users_table.php | 30 +++ ...1_055356_add_is_public_to_images_table.php | 28 ++ ..._add_two_factor_columns_to_users_table.php | 39 +++ package-lock.json | 71 +++-- package.json | 5 + public/js/app/custom-navigation-state.js | 83 ++++++ resources/css/app.css | 20 ++ resources/js/Components/GalleryGrid.vue | 25 ++ resources/js/Components/ImageContextMenu.vue | 69 +++-- resources/js/Components/Navigation.vue | 5 +- resources/js/Components/StyleSelector.vue | 70 ++--- resources/js/Pages/Home.vue | 141 +++++++--- resources/js/app.js | 10 + resources/js/custom/navigation-state.js | 83 ++++++ resources/lang/de/api.php | 6 + resources/lang/de/messages.php | 11 - resources/lang/de/settings.php | 3 +- resources/lang/en/api.php | 6 + resources/lang/en/messages.php | 11 - resources/lang/en/settings.php | 3 +- routes/api.php | 4 + routes/web.php | 12 +- stack.txt | 245 +++++++++++++++++ 44 files changed, 1319 insertions(+), 358 deletions(-) delete mode 100644 app/Filament/Resources/SettingResource/Pages/Settings.php create mode 100644 database/migrations/2025_08_01_055332_add_settings_to_users_table.php create mode 100644 database/migrations/2025_08_01_055356_add_is_public_to_images_table.php create mode 100644 database/migrations/2025_08_01_091436_add_two_factor_columns_to_users_table.php create mode 100644 public/js/app/custom-navigation-state.js create mode 100644 resources/js/custom/navigation-state.js delete mode 100644 resources/lang/de/messages.php delete mode 100644 resources/lang/en/messages.php create mode 100644 stack.txt diff --git a/app/Filament/Resources/AiModelResource/Pages/ListAiModels.php b/app/Filament/Resources/AiModelResource/Pages/ListAiModels.php index d3cc0ce..157e229 100644 --- a/app/Filament/Resources/AiModelResource/Pages/ListAiModels.php +++ b/app/Filament/Resources/AiModelResource/Pages/ListAiModels.php @@ -5,6 +5,8 @@ namespace App\Filament\Resources\AiModelResource\Pages; use App\Filament\Resources\AiModelResource; use Filament\Actions; use Filament\Resources\Pages\ListRecords; +use Illuminate\Contracts\Pagination\Paginator; +use Illuminate\Database\Eloquent\Builder; class ListAiModels extends ListRecords { @@ -16,4 +18,35 @@ class ListAiModels extends ListRecords Actions\CreateAction::make(), ]; } + + protected function shouldPersistTableFiltersInSession(): bool + { + return true; + } + + protected function shouldPersistTableSortInSession(): bool + { + return true; + } + + protected function shouldPersistTableSearchInSession(): bool + { + return true; + } + + protected function paginateTableQuery(Builder $query): Paginator + { + $paginator = parent::paginateTableQuery($query); + return $paginator; + } + + public function updatedTablePage($page) + { + $this->dispatch('table-pagination-updated', ['tableId' => $this->id, 'page' => $page]); + } + + protected function getTableQueryStringIdentifier(): ?string + { + return 'ai-models-table'; + } } diff --git a/app/Filament/Resources/ApiProviderResource/Pages/ListApiProviders.php b/app/Filament/Resources/ApiProviderResource/Pages/ListApiProviders.php index 41b162a..2c92f0a 100644 --- a/app/Filament/Resources/ApiProviderResource/Pages/ListApiProviders.php +++ b/app/Filament/Resources/ApiProviderResource/Pages/ListApiProviders.php @@ -5,6 +5,8 @@ namespace App\Filament\Resources\ApiProviderResource\Pages; use App\Filament\Resources\ApiProviderResource; use Filament\Actions; use Filament\Resources\Pages\ListRecords; +use Illuminate\Contracts\Pagination\Paginator; +use Illuminate\Database\Eloquent\Builder; class ListApiProviders extends ListRecords { @@ -16,4 +18,35 @@ class ListApiProviders extends ListRecords Actions\CreateAction::make(), ]; } + + protected function shouldPersistTableFiltersInSession(): bool + { + return true; + } + + protected function shouldPersistTableSortInSession(): bool + { + return true; + } + + protected function shouldPersistTableSearchInSession(): bool + { + return true; + } + + protected function paginateTableQuery(Builder $query): Paginator + { + $paginator = parent::paginateTableQuery($query); + return $paginator; + } + + public function updatedTablePage($page) + { + $this->dispatch('table-pagination-updated', ['tableId' => $this->id, 'page' => $page]); + } + + protected function getTableQueryStringIdentifier(): ?string + { + return 'api-providers-table'; + } } diff --git a/app/Filament/Resources/ImageResource.php b/app/Filament/Resources/ImageResource.php index 25d8a31..ecc5766 100644 --- a/app/Filament/Resources/ImageResource.php +++ b/app/Filament/Resources/ImageResource.php @@ -31,6 +31,9 @@ class ImageResource extends Resource ->required() ->image() ->directory('uploads'), + Forms\Components\Toggle::make('is_public') + ->label(__('Publicly Visible')) + ->default(false), ]); } diff --git a/app/Filament/Resources/ImageResource/Pages/ListImages.php b/app/Filament/Resources/ImageResource/Pages/ListImages.php index 77159c5..b6d5432 100644 --- a/app/Filament/Resources/ImageResource/Pages/ListImages.php +++ b/app/Filament/Resources/ImageResource/Pages/ListImages.php @@ -5,6 +5,8 @@ namespace App\Filament\Resources\ImageResource\Pages; use App\Filament\Resources\ImageResource; use Filament\Actions; use Filament\Resources\Pages\ListRecords; +use Illuminate\Contracts\Pagination\Paginator; +use Illuminate\Database\Eloquent\Builder; class ListImages extends ListRecords { @@ -16,4 +18,35 @@ class ListImages extends ListRecords Actions\CreateAction::make(), ]; } + + protected function shouldPersistTableFiltersInSession(): bool + { + return true; + } + + protected function shouldPersistTableSortInSession(): bool + { + return true; + } + + protected function shouldPersistTableSearchInSession(): bool + { + return true; + } + + protected function paginateTableQuery(Builder $query): Paginator + { + $paginator = parent::paginateTableQuery($query); + return $paginator; + } + + public function updatedTablePage($page) + { + $this->dispatch('table-pagination-updated', ['tableId' => $this->id, 'page' => $page]); + } + + protected function getTableQueryStringIdentifier(): ?string + { + return 'images-table'; + } } diff --git a/app/Filament/Resources/SettingResource.php b/app/Filament/Resources/SettingResource.php index a517712..688b3cb 100644 --- a/app/Filament/Resources/SettingResource.php +++ b/app/Filament/Resources/SettingResource.php @@ -12,18 +12,31 @@ use Filament\Tables; use Filament\Tables\Table; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\SoftDeletingScope; +use Filament\Forms\Components\TextInput; +use Filament\Forms\Components\Fieldset; class SettingResource extends Resource { protected static ?string $model = Setting::class; - protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack'; + protected static ?string $navigationIcon = 'heroicon-o-cog'; public static function form(Form $form): Form { return $form ->schema([ - // + Forms\Components\TextInput::make('key') + ->label(__('Key')) + ->required() + ->maxLength(255) + ->hiddenOn('edit'), + Forms\Components\Fieldset::make() + ->label(fn (?Setting $record) => $record ? $record->key : __('New Setting')) + ->schema([ + TextInput::make('value') + ->label(__('Value')) + ->disableLabel() + ]) ]); } @@ -31,7 +44,8 @@ class SettingResource extends Resource { return $table ->columns([ - // + Tables\Columns\TextColumn::make('key')->label(__('Key'))->searchable()->sortable(), + Tables\Columns\TextColumn::make('value')->label(__('Value'))->searchable()->sortable(), ]) ->filters([ // @@ -39,6 +53,7 @@ class SettingResource extends Resource ->actions([ Tables\Actions\EditAction::make(), ]) + ->bulkActions([ Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make(), diff --git a/app/Filament/Resources/SettingResource/Pages/Settings.php b/app/Filament/Resources/SettingResource/Pages/Settings.php deleted file mode 100644 index c783388..0000000 --- a/app/Filament/Resources/SettingResource/Pages/Settings.php +++ /dev/null @@ -1,64 +0,0 @@ -form->fill( - collect(Setting::all()) - ->mapWithKeys(fn (Setting $setting) => [$setting->key => $setting->value]) - ->all() - ); - } - - public function form(Form $form): Form - { - return $form - ->schema([ - TextInput::make('gallery_heading') - ->label(__('settings.gallery_heading')), - ]) - ->statePath('data') - ->model(Setting::class); - } - - public function submit(): void - { - foreach ($this->form->getState() as $key => $value) { - Setting::updateOrCreate(['key' => $key], ['value' => $value]); - } - - Notification::make() - ->title(__('settings.saved_successfully')) - ->success() - ->send(); - } -} diff --git a/app/Filament/Resources/StyleResource.php b/app/Filament/Resources/StyleResource.php index f1eb4a9..e3dc42c 100644 --- a/app/Filament/Resources/StyleResource.php +++ b/app/Filament/Resources/StyleResource.php @@ -110,9 +110,7 @@ class StyleResource extends Resource ]) ->emptyStateActions([ Tables\Actions\CreateAction::make(), - ]) - ->persistFiltersInSession() - ->persistSortInSession(); + ]); } public static function getRelations(): array diff --git a/app/Filament/Resources/StyleResource/Pages/ListStyles.php b/app/Filament/Resources/StyleResource/Pages/ListStyles.php index 47ed5a2..4413bb2 100644 --- a/app/Filament/Resources/StyleResource/Pages/ListStyles.php +++ b/app/Filament/Resources/StyleResource/Pages/ListStyles.php @@ -5,6 +5,8 @@ namespace App\Filament\Resources\StyleResource\Pages; use App\Filament\Resources\StyleResource; use Filament\Actions; use Filament\Resources\Pages\ListRecords; +use Illuminate\Contracts\Pagination\Paginator; +use Illuminate\Database\Eloquent\Builder; class ListStyles extends ListRecords { @@ -16,4 +18,35 @@ class ListStyles extends ListRecords Actions\CreateAction::make(), ]; } + + protected function shouldPersistTableFiltersInSession(): bool + { + return true; + } + + protected function shouldPersistTableSortInSession(): bool + { + return true; + } + + protected function shouldPersistTableSearchInSession(): bool + { + return true; + } + + protected function paginateTableQuery(Builder $query): Paginator + { + $paginator = parent::paginateTableQuery($query); + return $paginator; + } + + public function updatedTablePage($page) + { + $this->dispatch('table-pagination-updated', ['tableId' => $this->id, 'page' => $page]); + } + + protected function getTableQueryStringIdentifier(): ?string + { + return 'styles-table'; + } } diff --git a/app/Filament/Resources/UserResource.php b/app/Filament/Resources/UserResource.php index ed6c2d7..a0a2b76 100644 --- a/app/Filament/Resources/UserResource.php +++ b/app/Filament/Resources/UserResource.php @@ -27,24 +27,50 @@ class UserResource extends Resource { return $form ->schema([ - TextInput::make('name') - ->label(__('filament.resource.user.form.name')) - ->required() - ->maxLength(255), - TextInput::make('email') - ->label(__('filament.resource.user.form.email')) - ->email() - ->required() - ->maxLength(255), - TextInput::make('password') - ->label(__('filament.resource.user.form.password')) - ->password() - ->required() - ->maxLength(255), - Select::make('role_id') - ->relationship('role', 'name') - ->label(__('filament.resource.user.form.role')) - ->required(), + Forms\Components\Section::make('User Details') + ->schema([ + TextInput::make('name') + ->label(__('filament.resource.user.form.name')) + ->required() + ->maxLength(255), + TextInput::make('email') + ->label(__('filament.resource.user.form.email')) + ->email() + ->required() + ->maxLength(255), + TextInput::make('password') + ->label(__('filament.resource.user.form.password')) + ->password() + ->dehydrateStateUsing(fn (string $state): string => bcrypt($state)) + ->dehydrated(fn (?string $state): bool => filled($state)) + ->required(fn (string $operation): bool => $operation === 'create') + ->maxLength(255), + Select::make('role_id') + ->relationship('role', 'name') + ->label(__('filament.resource.user.form.role')) + ->required(), + ])->columns(2), + + Forms\Components\Section::make('Preferences') + ->schema([ + Forms\Components\Toggle::make('email_notifications_enabled') + ->label(__('Email Notifications')) + ->default(true), + Select::make('theme_preference') + ->label(__('Theme')) + ->options([ + 'light' => 'Light', + 'dark' => 'Dark', + ]) + ->default('light'), + Select::make('locale') + ->label(__('Language')) + ->options([ + 'en' => 'English', + 'de' => 'Deutsch', + ]) + ->default('en'), + ])->columns(2), ]); } diff --git a/app/Filament/Resources/UserResource/Pages/ListUsers.php b/app/Filament/Resources/UserResource/Pages/ListUsers.php index 0766ffe..2613a13 100644 --- a/app/Filament/Resources/UserResource/Pages/ListUsers.php +++ b/app/Filament/Resources/UserResource/Pages/ListUsers.php @@ -5,6 +5,8 @@ namespace App\Filament\Resources\UserResource\Pages; use App\Filament\Resources\UserResource; use Filament\Actions; use Filament\Resources\Pages\ListRecords; +use Illuminate\Contracts\Pagination\Paginator; +use Illuminate\Database\Eloquent\Builder; class ListUsers extends ListRecords { @@ -16,4 +18,35 @@ class ListUsers extends ListRecords Actions\CreateAction::make(), ]; } + + protected function shouldPersistTableFiltersInSession(): bool + { + return true; + } + + protected function shouldPersistTableSortInSession(): bool + { + return true; + } + + protected function shouldPersistTableSearchInSession(): bool + { + return true; + } + + protected function paginateTableQuery(Builder $query): Paginator + { + $paginator = parent::paginateTableQuery($query); + return $paginator; + } + + public function updatedTablePage($page) + { + $this->dispatch('table-pagination-updated', ['tableId' => $this->id, 'page' => $page]); + } + + protected function getTableQueryStringIdentifier(): ?string + { + return 'users-table'; + } } diff --git a/app/Http/Controllers/Api/ImageController.php b/app/Http/Controllers/Api/ImageController.php index 5b525dd..072d5d5 100644 --- a/app/Http/Controllers/Api/ImageController.php +++ b/app/Http/Controllers/Api/ImageController.php @@ -9,10 +9,12 @@ use App\Models\ApiProvider; use App\Models\Style; use App\Models\Image; use Illuminate\Support\Facades\File; +use Carbon\Carbon; +use App\Models\Setting; class ImageController extends Controller { - public function index() + public function index(Request $request) { $publicUploadsPath = public_path('storage/uploads'); @@ -34,15 +36,28 @@ class ImageController extends Controller // Add images from disk that are not in the database $imagesToAdd = array_diff($diskImagePaths, $dbImagePaths); foreach ($imagesToAdd as $path) { - Image::create(['path' => $path]); + Image::create(['path' => $path, 'is_public' => true]); } // Remove images from database that are not on disk $imagesToRemove = array_diff($dbImagePaths, $diskImagePaths); Image::whereIn('path', $imagesToRemove)->delete(); - // Fetch all images from the database after synchronization - $images = Image::orderBy('updated_at', 'desc')->get(); + // Fetch images from the database after synchronization + $query = Image::orderBy('updated_at', 'desc'); + + // If user is not authenticated, filter by is_public + if (!auth()->check()) { + $query->where('is_public', true); + } + + $newImageTimespanMinutes = Setting::where('key', 'new_image_timespan_minutes')->first()->value ?? 60; // Default to 60 minutes + + $images = $query->get()->map(function ($image) use ($newImageTimespanMinutes) { + $image->is_new = Carbon::parse($image->created_at)->diffInMinutes(Carbon::now()) <= $newImageTimespanMinutes; + return $image; + }); + $formattedImages = []; foreach ($images as $image) { $formattedImages[] = [ @@ -50,6 +65,8 @@ class ImageController extends Controller 'path' => asset('storage/' . $image->path), 'name' => basename($image->path), 'is_temp' => (bool) $image->is_temp, + 'is_public' => (bool) $image->is_public, + 'is_new' => (bool) $image->is_new, ]; } return response()->json($formattedImages); @@ -75,6 +92,7 @@ class ImageController extends Controller $image = Image::create([ 'path' => $relativePath, + 'is_public' => true, ]); return response()->json([ @@ -96,15 +114,29 @@ class ImageController extends Controller $request->validate([ 'image_id' => 'required|exists:images,id', - 'style_id' => 'required|exists:styles,id', + 'style_id' => 'nullable|exists:styles,id', ]); $image = Image::find($request->image_id); - $style = Style::with(['aiModel' => function ($query) { - $query->where('enabled', true)->with(['apiProviders' => function ($query) { - $query->where('enabled', true); - }]); - }])->find($request->style_id); + $style = null; + + if ($request->style_id) { + $style = Style::with(['aiModel' => function ($query) { + $query->where('enabled', true)->with(['apiProviders' => function ($query) { + $query->where('enabled', true); + }]); + }])->find($request->style_id); + } else { + // Attempt to get default style from settings + $defaultStyleSetting = \App\Models\Setting::where('key', 'default_style_id')->first(); + if ($defaultStyleSetting && $defaultStyleSetting->value) { + $style = Style::with(['aiModel' => function ($query) { + $query->where('enabled', true)->with(['apiProviders' => function ($query) { + $query->where('enabled', true); + }]); + }])->find($defaultStyleSetting->value); + } + } if (!$style || !$style->aiModel || $style->aiModel->apiProviders->isEmpty()) { return response()->json(['error' => __('api.style_or_provider_not_found')], 404); diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 9a425ac..c7055ad 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -4,20 +4,33 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Setting; +use App\Models\Image; use Inertia\Inertia; use Illuminate\Support\Facades\Lang; +use Carbon\Carbon; class HomeController extends Controller { public function index() { $locale = app()->getLocale(); - $translations = Lang::get('messages', [], $locale); + $translations = array_merge( + Lang::get('api', [], $locale), + Lang::get('settings', [], $locale) + ); $galleryHeading = Setting::where('key', 'gallery_heading')->first()->value ?? 'Style Gallery'; + $newImageTimespanMinutes = Setting::where('key', 'new_image_timespan_minutes')->first()->value ?? 60; // Default to 60 minutes + + $images = Image::all()->map(function ($image) use ($newImageTimespanMinutes) { + $image->is_new = Carbon::parse($image->created_at)->diffInMinutes(Carbon::now()) <= $newImageTimespanMinutes; + $image->path = 'storage/' . $image->path; + return $image; + }); return Inertia::render('Home', [ 'translations' => $translations, 'galleryHeading' => $galleryHeading, + 'images' => $images, ]); } } \ No newline at end of file diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index e3fcd92..944de36 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -35,14 +35,21 @@ class HandleInertiaRequests extends Middleware 'user' => $request->user(), ], 'locale' => app()->getLocale(), - 'lang' => function () { + 'translations' => function () use ($request) { + $currentLocale = app()->getLocale(); // Store current locale + $requestedLocale = $request->input('locale', $currentLocale); + app()->setLocale($requestedLocale); // Set locale based on request or current + $lang = [ 'filament' => trans('filament'), 'api' => trans('api'), 'settings' => trans('settings'), - 'messages' => trans('messages'), // Add other translation files as needed ]; + + dd($lang); // <-- ADDED FOR DEBUGGING + + app()->setLocale($currentLocale); // Revert to original locale return $lang; }, ]; diff --git a/app/Http/Middleware/SetLocale.php b/app/Http/Middleware/SetLocale.php index 651adcb..927f0ff 100644 --- a/app/Http/Middleware/SetLocale.php +++ b/app/Http/Middleware/SetLocale.php @@ -15,12 +15,16 @@ class SetLocale */ public function handle(Request $request, Closure $next): Response { - $locale = substr($request->server('HTTP_ACCEPT_LANGUAGE'), 0, 2); - - if (in_array($locale, ['de'])) { - app()->setLocale($locale); + if (auth()->check() && auth()->user()->locale) { + app()->setLocale(auth()->user()->locale); } else { - app()->setLocale('en'); + $locale = substr($request->server('HTTP_ACCEPT_LANGUAGE'), 0, 2); + + if (in_array($locale, ['de'])) { + app()->setLocale($locale); + } else { + app()->setLocale('en'); + } } return $next($request); diff --git a/app/Models/Image.php b/app/Models/Image.php index 6669d51..fbebaee 100644 --- a/app/Models/Image.php +++ b/app/Models/Image.php @@ -16,5 +16,6 @@ class Image extends Model 'original_image_id', 'style_id', 'is_temp', + 'is_public', ]; } diff --git a/app/Models/User.php b/app/Models/User.php index 23e432a..c9bfe78 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -22,6 +22,9 @@ class User extends Authenticatable 'email', 'password', 'role_id', + 'email_notifications_enabled', + 'theme_preference', + 'locale', ]; public function role() diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index cd52120..2ac0167 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,7 @@ namespace App\Providers; +use Filament\Support\Facades\FilamentAsset; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -19,6 +20,10 @@ class AppServiceProvider extends ServiceProvider */ public function boot(): void { + FilamentAsset::register([ + \Filament\Support\Assets\Js::make('custom-navigation-state', __DIR__ . '/../../resources/js/custom/navigation-state.js'), + ]); + $locale = substr(request()->server('HTTP_ACCEPT_LANGUAGE'), 0, 2); if (in_array($locale, ['de'])) { diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index aaa3ec5..f22776a 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -19,12 +19,13 @@ use Illuminate\Session\Middleware\StartSession; use Illuminate\View\Middleware\ShareErrorsFromSession; use App\Filament\Resources\StyleResource; use App\Filament\Resources\SettingResource\Pages\Settings; +use Illuminate\Support\Facades\Auth; class AdminPanelProvider extends PanelProvider { public function panel(Panel $panel): Panel { - return $panel + $panel = $panel ->default() ->id('admin') ->path('admin') @@ -37,7 +38,7 @@ class AdminPanelProvider extends PanelProvider ->pages([ Pages\Dashboard::class, \App\Filament\Pages\InstallPluginPage::class, - Settings::class, + ]) ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets') ->widgets([ @@ -60,6 +61,19 @@ class AdminPanelProvider extends PanelProvider ]) ->plugins([ - ]); + ]) + ->profile(); + + if (Auth::check()) { + $user = Auth::user(); + if ($user->theme_preference === 'dark') { + $panel->darkMode(); + } else { + $panel->lightMode(); + } + } + + return $panel; } } + diff --git a/composer.json b/composer.json index 8cd6b45..65e841b 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,6 @@ "license": "MIT", "require": { "php": "^8.1", - "filament/filament": "3.0", "guzzlehttp/guzzle": "^7.2", "inertiajs/inertia-laravel": "^0.6.8", diff --git a/composer.lock b/composer.lock index cbe6b62..f0b9f75 100644 --- a/composer.lock +++ b/composer.lock @@ -4918,16 +4918,16 @@ }, { "name": "symfony/console", - "version": "v6.4.23", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "9056771b8eca08d026cd3280deeec3cfd99c4d93" + "reference": "59266a5bf6a596e3e0844fd95e6ad7ea3c1d3350" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/9056771b8eca08d026cd3280deeec3cfd99c4d93", - "reference": "9056771b8eca08d026cd3280deeec3cfd99c4d93", + "url": "https://api.github.com/repos/symfony/console/zipball/59266a5bf6a596e3e0844fd95e6ad7ea3c1d3350", + "reference": "59266a5bf6a596e3e0844fd95e6ad7ea3c1d3350", "shasum": "" }, "require": { @@ -4992,7 +4992,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.23" + "source": "https://github.com/symfony/console/tree/v6.4.24" }, "funding": [ { @@ -5003,12 +5003,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-27T19:37:22+00:00" + "time": "2025-07-30T10:38:54+00:00" }, { "name": "symfony/css-selector", @@ -5144,16 +5148,16 @@ }, { "name": "symfony/error-handler", - "version": "v6.4.23", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "b088e0b175c30b4e06d8085200fa465b586f44fa" + "reference": "30fd0b3cf0e972e82636038ce4db0e4fe777112c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/b088e0b175c30b4e06d8085200fa465b586f44fa", - "reference": "b088e0b175c30b4e06d8085200fa465b586f44fa", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/30fd0b3cf0e972e82636038ce4db0e4fe777112c", + "reference": "30fd0b3cf0e972e82636038ce4db0e4fe777112c", "shasum": "" }, "require": { @@ -5199,7 +5203,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.4.23" + "source": "https://github.com/symfony/error-handler/tree/v6.4.24" }, "funding": [ { @@ -5210,12 +5214,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-13T07:39:48+00:00" + "time": "2025-07-24T08:25:04+00:00" }, { "name": "symfony/event-dispatcher", @@ -5375,16 +5383,16 @@ }, { "name": "symfony/finder", - "version": "v6.4.17", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7" + "reference": "73089124388c8510efb8d2d1689285d285937b08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", - "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", + "url": "https://api.github.com/repos/symfony/finder/zipball/73089124388c8510efb8d2d1689285d285937b08", + "reference": "73089124388c8510efb8d2d1689285d285937b08", "shasum": "" }, "require": { @@ -5419,7 +5427,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.17" + "source": "https://github.com/symfony/finder/tree/v6.4.24" }, "funding": [ { @@ -5430,25 +5438,29 @@ "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": "2024-12-29T13:51:37+00:00" + "time": "2025-07-15T12:02:45+00:00" }, { "name": "symfony/html-sanitizer", - "version": "v6.4.21", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/html-sanitizer.git", - "reference": "f66d6585c6ece946239317c339f8b2860dfdf2db" + "reference": "8e9bb309986809af4cd9e049f9362d736387f083" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/f66d6585c6ece946239317c339f8b2860dfdf2db", - "reference": "f66d6585c6ece946239317c339f8b2860dfdf2db", + "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/8e9bb309986809af4cd9e049f9362d736387f083", + "reference": "8e9bb309986809af4cd9e049f9362d736387f083", "shasum": "" }, "require": { @@ -5488,7 +5500,7 @@ "sanitizer" ], "support": { - "source": "https://github.com/symfony/html-sanitizer/tree/v6.4.21" + "source": "https://github.com/symfony/html-sanitizer/tree/v6.4.24" }, "funding": [ { @@ -5499,25 +5511,29 @@ "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-03-31T07:29:45+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.4.23", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "452d19f945ee41345fd8a50c18b60783546b7bd3" + "reference": "0341e41d8d8830c31a1dff5cbc5bdb3ec872a073" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/452d19f945ee41345fd8a50c18b60783546b7bd3", - "reference": "452d19f945ee41345fd8a50c18b60783546b7bd3", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/0341e41d8d8830c31a1dff5cbc5bdb3ec872a073", + "reference": "0341e41d8d8830c31a1dff5cbc5bdb3ec872a073", "shasum": "" }, "require": { @@ -5565,7 +5581,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.23" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.24" }, "funding": [ { @@ -5576,25 +5592,29 @@ "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-05-26T09:17:58+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.23", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "2bb2cba685aabd859f22cf6946554e8e7f3c329a" + "reference": "b81dcdbe34b8e8f7b3fc7b2a47fa065d5bf30726" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/2bb2cba685aabd859f22cf6946554e8e7f3c329a", - "reference": "2bb2cba685aabd859f22cf6946554e8e7f3c329a", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b81dcdbe34b8e8f7b3fc7b2a47fa065d5bf30726", + "reference": "b81dcdbe34b8e8f7b3fc7b2a47fa065d5bf30726", "shasum": "" }, "require": { @@ -5679,7 +5699,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.4.23" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.24" }, "funding": [ { @@ -5690,25 +5710,29 @@ "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-28T08:14:51+00:00" + "time": "2025-07-31T09:23:30+00:00" }, { "name": "symfony/mailer", - "version": "v6.4.23", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "a480322ddf8e54de262c9bca31fdcbe26b553de5" + "reference": "b4d7fa2c69641109979ed06e98a588d245362062" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/a480322ddf8e54de262c9bca31fdcbe26b553de5", - "reference": "a480322ddf8e54de262c9bca31fdcbe26b553de5", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b4d7fa2c69641109979ed06e98a588d245362062", + "reference": "b4d7fa2c69641109979ed06e98a588d245362062", "shasum": "" }, "require": { @@ -5759,7 +5783,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.4.23" + "source": "https://github.com/symfony/mailer/tree/v6.4.24" }, "funding": [ { @@ -5770,25 +5794,29 @@ "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-26T21:24:02+00:00" + "time": "2025-07-24T08:25:04+00:00" }, { "name": "symfony/mime", - "version": "v6.4.21", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "fec8aa5231f3904754955fad33c2db50594d22d1" + "reference": "664d5e844a2de5e11c8255d0aef6bc15a9660ac7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/fec8aa5231f3904754955fad33c2db50594d22d1", - "reference": "fec8aa5231f3904754955fad33c2db50594d22d1", + "url": "https://api.github.com/repos/symfony/mime/zipball/664d5e844a2de5e11c8255d0aef6bc15a9660ac7", + "reference": "664d5e844a2de5e11c8255d0aef6bc15a9660ac7", "shasum": "" }, "require": { @@ -5844,7 +5872,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.4.21" + "source": "https://github.com/symfony/mime/tree/v6.4.24" }, "funding": [ { @@ -5855,12 +5883,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-04-27T13:27:38+00:00" + "time": "2025-07-15T12:02:45+00:00" }, { "name": "symfony/polyfill-ctype", @@ -6501,16 +6533,16 @@ }, { "name": "symfony/process", - "version": "v6.4.20", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20" + "reference": "8eb6dc555bfb49b2703438d5de65cc9f138ff50b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/e2a61c16af36c9a07e5c9906498b73e091949a20", - "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20", + "url": "https://api.github.com/repos/symfony/process/zipball/8eb6dc555bfb49b2703438d5de65cc9f138ff50b", + "reference": "8eb6dc555bfb49b2703438d5de65cc9f138ff50b", "shasum": "" }, "require": { @@ -6542,7 +6574,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.20" + "source": "https://github.com/symfony/process/tree/v6.4.24" }, "funding": [ { @@ -6553,25 +6585,29 @@ "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-03-10T17:11:00+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "symfony/routing", - "version": "v6.4.22", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "1f5234e8457164a3a0038a4c0a4ba27876a9c670" + "reference": "e4f94e625c8e6f910aa004a0042f7b2d398278f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/1f5234e8457164a3a0038a4c0a4ba27876a9c670", - "reference": "1f5234e8457164a3a0038a4c0a4ba27876a9c670", + "url": "https://api.github.com/repos/symfony/routing/zipball/e4f94e625c8e6f910aa004a0042f7b2d398278f5", + "reference": "e4f94e625c8e6f910aa004a0042f7b2d398278f5", "shasum": "" }, "require": { @@ -6625,7 +6661,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.22" + "source": "https://github.com/symfony/routing/tree/v6.4.24" }, "funding": [ { @@ -6636,12 +6672,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-04-27T16:08:38+00:00" + "time": "2025-07-15T08:46:37+00:00" }, { "name": "symfony/service-contracts", @@ -6728,16 +6768,16 @@ }, { "name": "symfony/string", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", + "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca", + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca", "shasum": "" }, "require": { @@ -6795,7 +6835,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.0" + "source": "https://github.com/symfony/string/tree/v7.3.2" }, "funding": [ { @@ -6806,25 +6846,29 @@ "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-04-20T20:19:01+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "symfony/translation", - "version": "v6.4.23", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "de8afa521e04a5220e9e58a1dc99971ab7cac643" + "reference": "300b72643e89de0734d99a9e3f8494a3ef6936e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/de8afa521e04a5220e9e58a1dc99971ab7cac643", - "reference": "de8afa521e04a5220e9e58a1dc99971ab7cac643", + "url": "https://api.github.com/repos/symfony/translation/zipball/300b72643e89de0734d99a9e3f8494a3ef6936e1", + "reference": "300b72643e89de0734d99a9e3f8494a3ef6936e1", "shasum": "" }, "require": { @@ -6890,7 +6934,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.4.23" + "source": "https://github.com/symfony/translation/tree/v6.4.24" }, "funding": [ { @@ -6901,12 +6945,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-26T21:24:02+00:00" + "time": "2025-07-30T17:30:48+00:00" }, { "name": "symfony/translation-contracts", @@ -6988,16 +7036,16 @@ }, { "name": "symfony/uid", - "version": "v6.4.23", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "9c8592da78d7ee6af52011eef593350d87e814c0" + "reference": "17da16a750541a42cf2183935e0f6008316c23f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/9c8592da78d7ee6af52011eef593350d87e814c0", - "reference": "9c8592da78d7ee6af52011eef593350d87e814c0", + "url": "https://api.github.com/repos/symfony/uid/zipball/17da16a750541a42cf2183935e0f6008316c23f7", + "reference": "17da16a750541a42cf2183935e0f6008316c23f7", "shasum": "" }, "require": { @@ -7042,7 +7090,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.4.23" + "source": "https://github.com/symfony/uid/tree/v6.4.24" }, "funding": [ { @@ -7053,25 +7101,29 @@ "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-26T08:06:12+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.23", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "d55b1834cdbfcc31bc2cd7e095ba5ed9a88f6600" + "reference": "aa29484ce0544bd69fa9f0df902e5ed7b7fe5034" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/d55b1834cdbfcc31bc2cd7e095ba5ed9a88f6600", - "reference": "d55b1834cdbfcc31bc2cd7e095ba5ed9a88f6600", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/aa29484ce0544bd69fa9f0df902e5ed7b7fe5034", + "reference": "aa29484ce0544bd69fa9f0df902e5ed7b7fe5034", "shasum": "" }, "require": { @@ -7083,7 +7135,6 @@ "symfony/console": "<5.4" }, "require-dev": { - "ext-iconv": "*", "symfony/console": "^5.4|^6.0|^7.0", "symfony/error-handler": "^6.3|^7.0", "symfony/http-kernel": "^5.4|^6.0|^7.0", @@ -7127,7 +7178,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.23" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.24" }, "funding": [ { @@ -7138,12 +7189,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-27T15:05:27+00:00" + "time": "2025-07-29T18:40:01+00:00" }, { "name": "tightenco/ziggy", @@ -7890,16 +7945,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.3", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", - "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -7938,7 +7993,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -7946,7 +8001,7 @@ "type": "tidelift" } ], - "time": "2025-07-05T12:25:42+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nunomaduro/collision", @@ -9890,16 +9945,16 @@ }, { "name": "symfony/yaml", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb" + "reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/0c3555045a46ab3cd4cc5a69d161225195230edb", - "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb", + "url": "https://api.github.com/repos/symfony/yaml/zipball/b8d7d868da9eb0919e99c8830431ea087d6aae30", + "reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30", "shasum": "" }, "require": { @@ -9942,7 +9997,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.3.1" + "source": "https://github.com/symfony/yaml/tree/v7.3.2" }, "funding": [ { @@ -9953,12 +10008,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-03T06:57:57+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "theseer/tokenizer", diff --git a/database/migrations/2025_07_30_143255_create_settings_table.php b/database/migrations/2025_07_30_143255_create_settings_table.php index ce3b578..3ff1773 100644 --- a/database/migrations/2025_07_30_143255_create_settings_table.php +++ b/database/migrations/2025_07_30_143255_create_settings_table.php @@ -12,8 +12,7 @@ return new class extends Migration public function up(): void { Schema::create('settings', function (Blueprint $table) { - $table->id(); - $table->string('key')->unique(); + $table->string('key')->primary(); $table->text('value')->nullable(); $table->timestamps(); }); diff --git a/database/migrations/2025_08_01_055332_add_settings_to_users_table.php b/database/migrations/2025_08_01_055332_add_settings_to_users_table.php new file mode 100644 index 0000000..ac5e29b --- /dev/null +++ b/database/migrations/2025_08_01_055332_add_settings_to_users_table.php @@ -0,0 +1,30 @@ +boolean('email_notifications_enabled')->default(true); + $table->string('theme_preference')->default('light'); + $table->string('locale')->default('en'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn(['email_notifications_enabled', 'theme_preference', 'locale']); + }); + } +}; diff --git a/database/migrations/2025_08_01_055356_add_is_public_to_images_table.php b/database/migrations/2025_08_01_055356_add_is_public_to_images_table.php new file mode 100644 index 0000000..1a95bf8 --- /dev/null +++ b/database/migrations/2025_08_01_055356_add_is_public_to_images_table.php @@ -0,0 +1,28 @@ +boolean('is_public')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('images', function (Blueprint $table) { + $table->dropColumn('is_public'); + }); + } +}; diff --git a/database/migrations/2025_08_01_091436_add_two_factor_columns_to_users_table.php b/database/migrations/2025_08_01_091436_add_two_factor_columns_to_users_table.php new file mode 100644 index 0000000..7fa1049 --- /dev/null +++ b/database/migrations/2025_08_01_091436_add_two_factor_columns_to_users_table.php @@ -0,0 +1,39 @@ +text('two_factor_secret') + ->nullable(); + + $table->text('two_factor_recovery_codes') + ->nullable(); + + $table->timestamp('two_factor_confirmed_at') + ->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn([ + 'two_factor_secret', + 'two_factor_recovery_codes', + 'two_factor_confirmed_at', + ]); + }); + } +}; diff --git a/package-lock.json b/package-lock.json index 247313f..08179be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,11 @@ "requires": true, "packages": { "": { + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^7.0.0", + "@fortawesome/free-solid-svg-icons": "^7.0.0", + "@fortawesome/vue-fontawesome": "^3.1.1" + }, "devDependencies": { "@inertiajs/vue3": "^1.0.0", "@tailwindcss/forms": "^0.5.3", @@ -34,7 +39,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -44,7 +48,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -54,7 +57,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.0" @@ -70,7 +72,6 @@ "version": "7.28.2", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -471,6 +472,49 @@ "node": ">=12" } }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.0.0.tgz", + "integrity": "sha512-PGMrIYXLGA5K8RWy8zwBkd4vFi4z7ubxtet6Yn13Plf6krRTwPbdlCwlcfmoX0R7B4Z643QvrtHmdQ5fNtfFCg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.0.0.tgz", + "integrity": "sha512-obBEF+zd98r/KtKVW6A+8UGWeaOoyMpl6Q9P3FzHsOnsg742aXsl8v+H/zp09qSSu/a/Hxe9LNKzbBaQq1CEbA==", + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-common-types": "7.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-7.0.0.tgz", + "integrity": "sha512-njSLAllkOddYDCXgTFboXn54Oe5FcvpkWq+FoetOHR64PbN0608kM02Lze0xtISGpXgP+i26VyXRQA0Irh3Obw==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "7.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/vue-fontawesome": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.1.1.tgz", + "integrity": "sha512-U5azn4mcUVpjHe4JO0Wbe7Ih8e3VbN83EH7OTBtA5/QGw9qcPGffqcmwsLyZYgEkpVkYbq/6dX1Iyl5KUGMp6Q==", + "license": "MIT", + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6 || ~7", + "vue": ">= 3.0.0 < 4" + } + }, "node_modules/@inertiajs/core": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-1.3.0.tgz", @@ -542,7 +586,6 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -923,7 +966,6 @@ "version": "3.5.18", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.18.tgz", "integrity": "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.0", @@ -937,7 +979,6 @@ "version": "3.5.18", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz", "integrity": "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==", - "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-core": "3.5.18", @@ -948,7 +989,6 @@ "version": "3.5.18", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.18.tgz", "integrity": "sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.0", @@ -966,7 +1006,6 @@ "version": "3.5.18", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.18.tgz", "integrity": "sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==", - "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-dom": "3.5.18", @@ -977,7 +1016,6 @@ "version": "3.5.18", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.18.tgz", "integrity": "sha512-x0vPO5Imw+3sChLM5Y+B6G1zPjwdOri9e8V21NnTnlEvkxatHEH5B5KEAJcjuzQ7BsjGrKtfzuQ5eQwXh8HXBg==", - "dev": true, "license": "MIT", "dependencies": { "@vue/shared": "3.5.18" @@ -987,7 +1025,6 @@ "version": "3.5.18", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.18.tgz", "integrity": "sha512-DUpHa1HpeOQEt6+3nheUfqVXRog2kivkXHUhoqJiKR33SO4x+a5uNOMkV487WPerQkL0vUuRvq/7JhRgLW3S+w==", - "dev": true, "license": "MIT", "dependencies": { "@vue/reactivity": "3.5.18", @@ -998,7 +1035,6 @@ "version": "3.5.18", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.18.tgz", "integrity": "sha512-YwDj71iV05j4RnzZnZtGaXwPoUWeRsqinblgVJwR8XTXYZ9D5PbahHQgsbmzUvCWNF6x7siQ89HgnX5eWkr3mw==", - "dev": true, "license": "MIT", "dependencies": { "@vue/reactivity": "3.5.18", @@ -1011,7 +1047,6 @@ "version": "3.5.18", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.18.tgz", "integrity": "sha512-PvIHLUoWgSbDG7zLHqSqaCoZvHi6NNmfVFOqO+OnwvqMz/tqQr3FuGWS8ufluNddk7ZLBJYMrjcw1c6XzR12mA==", - "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-ssr": "3.5.18", @@ -1025,7 +1060,6 @@ "version": "3.5.18", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.18.tgz", "integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==", - "dev": true, "license": "MIT" }, "node_modules/ansi-regex": { @@ -1390,7 +1424,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/deepmerge": { @@ -1467,7 +1500,6 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -1578,7 +1610,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -2027,7 +2058,6 @@ "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -2142,7 +2172,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -2269,7 +2298,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -2309,7 +2337,6 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2734,7 +2761,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -3074,7 +3100,6 @@ "version": "3.5.18", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.18.tgz", "integrity": "sha512-7W4Y4ZbMiQ3SEo+m9lnoNpV9xG7QVMLa+/0RFwwiAVkeYoyGXqWE85jabU4pllJNUzqfLShJ5YLptewhCWUgNA==", - "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-dom": "3.5.18", diff --git a/package.json b/package.json index da24885..1bb1cb9 100644 --- a/package.json +++ b/package.json @@ -15,5 +15,10 @@ "tailwindcss": "^3.2.1", "vite": "^5.0.0", "vue": "^3.4.0" + }, + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^7.0.0", + "@fortawesome/free-solid-svg-icons": "^7.0.0", + "@fortawesome/vue-fontawesome": "^3.1.1" } } diff --git a/public/js/app/custom-navigation-state.js b/public/js/app/custom-navigation-state.js new file mode 100644 index 0000000..8abdc88 --- /dev/null +++ b/public/js/app/custom-navigation-state.js @@ -0,0 +1,83 @@ +document.addEventListener('DOMContentLoaded', () => { + const navigation = document.querySelector('.fi-main-nav'); + if (!navigation) return; + + const KEY = 'navigation_state'; + const TABLE_PAGES_KEY = 'table_pages_state'; + + // Function to get the state from session via a custom endpoint + const getState = () => { + return JSON.parse(sessionStorage.getItem(KEY)) || {}; + }; + + // Function to save the state to session via a custom endpoint + const saveState = (state) => { + sessionStorage.setItem(KEY, JSON.stringify(state)); + const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); + fetch('/api/admin/navigation-state', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': csrfToken, + }, + body: JSON.stringify({ groups: state }), + }); + }; + + // Function to get table page state + const getTablePageState = () => { + return JSON.parse(sessionStorage.getItem(TABLE_PAGES_KEY)) || {}; + }; + + // Function to save table page state + const saveTablePageState = (tableId, page) => { + let tablePages = getTablePageState(); + tablePages[tableId] = page; + sessionStorage.setItem(TABLE_PAGES_KEY, JSON.stringify(tablePages)); + }; + + let currentState = getState(); + let currentTablePages = getTablePageState(); + + // Apply the saved navigation state on page load + const groups = navigation.querySelectorAll('.fi-nav-group'); + groups.forEach(group => { + const label = group.querySelector('.fi-nav-group-label').textContent.trim(); + if (currentState[label] === 'collapsed') { + group.classList.add('collapsed'); + } + }); + + // Add event listeners to save navigation state on click + navigation.addEventListener('click', (e) => { + const labelElement = e.target.closest('.fi-nav-group-label'); + if (!labelElement) return; + + const group = labelElement.closest('.fi-nav-group'); + const label = labelElement.textContent.trim(); + + if (group.classList.contains('collapsed')) { + currentState[label] = 'expanded'; + group.classList.remove('collapsed'); + } else { + currentState[label] = 'collapsed'; + group.classList.add('collapsed'); + } + + saveState(currentState); + }); + + // Livewire hook to save table page when pagination changes + Livewire.hook('component.initialized', (component) => { + if (component.name.includes('table')) { + const tableId = component.el.id; + if (currentTablePages[tableId]) { + component.set('tablePage', currentTablePages[tableId], false); + } + } + }); + + Livewire.on('table-pagination-updated', (tableId, page) => { + saveTablePageState(tableId, page); + }); +}); diff --git a/resources/css/app.css b/resources/css/app.css index b5c61c9..d1cf579 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -1,3 +1,23 @@ +@import '@fortawesome/fontawesome-svg-core/styles.css'; + @tailwind base; @tailwind components; @tailwind utilities; + +/* Theme variables */ +:root { + --color-background: #ffffff; + --color-text: #000000; +} + +html.dark { + --color-background: #333333; /* Dark grey */ + --color-text: #ffffff; +} + +/* Apply theme to body */ +body { + background-color: var(--color-background); + color: var(--color-text); + transition: background-color 0.3s ease, color 0.3s ease; +} \ No newline at end of file diff --git a/resources/js/Components/GalleryGrid.vue b/resources/js/Components/GalleryGrid.vue index 7efdd1b..a4acc60 100644 --- a/resources/js/Components/GalleryGrid.vue +++ b/resources/js/Components/GalleryGrid.vue @@ -8,20 +8,31 @@ @click="$emit('imageTapped', image, $event)" > +
{{ __('new') }}
\ No newline at end of file diff --git a/resources/js/Components/ImageContextMenu.vue b/resources/js/Components/ImageContextMenu.vue index 8770ea6..7494642 100644 --- a/resources/js/Components/ImageContextMenu.vue +++ b/resources/js/Components/ImageContextMenu.vue @@ -4,16 +4,45 @@
- +
+ + +
\ No newline at end of file diff --git a/resources/js/Components/Navigation.vue b/resources/js/Components/Navigation.vue index 5c8f4ef..d925cfb 100644 --- a/resources/js/Components/Navigation.vue +++ b/resources/js/Components/Navigation.vue @@ -27,9 +27,10 @@ const emits = defineEmits(['prevPage', 'nextPage']); justify-content: center; align-items: center; padding: 20px; - background-color: #f0f0f0; + background-color: var(--color-background); width: 100%; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + color: var(--color-text); } .navigation button { @@ -51,6 +52,6 @@ const emits = defineEmits(['prevPage', 'nextPage']); .navigation span { font-size: 1.2em; font-weight: bold; - color: #333; + color: var(--color-text); } \ No newline at end of file diff --git a/resources/js/Components/StyleSelector.vue b/resources/js/Components/StyleSelector.vue index f55d085..f4d6cec 100644 --- a/resources/js/Components/StyleSelector.vue +++ b/resources/js/Components/StyleSelector.vue @@ -1,22 +1,24 @@