language files combined, settings fixed, "new" badge integrated

This commit is contained in:
2025-08-01 23:34:41 +02:00
parent b2968f203d
commit 80873877c1
44 changed files with 1319 additions and 358 deletions

View File

@@ -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';
}
}

View File

@@ -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';
}
}

View File

@@ -31,6 +31,9 @@ class ImageResource extends Resource
->required()
->image()
->directory('uploads'),
Forms\Components\Toggle::make('is_public')
->label(__('Publicly Visible'))
->default(false),
]);
}

View File

@@ -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';
}
}

View File

@@ -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(),

View File

@@ -1,64 +0,0 @@
<?php
namespace App\Filament\Resources\SettingResource\Pages;
use App\Filament\Resources\SettingResource;
use App\Models\Setting;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Illuminate\Database\Eloquent\Model;
class Settings extends Page implements HasForms
{
use InteractsWithForms;
protected static string $resource = SettingResource::class;
protected static ?string $navigationIcon = 'heroicon-o-cog';
protected static ?string $navigationGroup = 'Settings';
protected static ?string $navigationLabel = 'Global Settings';
protected static ?string $slug = 'global-settings';
protected static string $view = 'filament.resources.setting-resource.pages.settings';
public ?array $data = [];
public function mount(): void
{
$this->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();
}
}

View File

@@ -110,9 +110,7 @@ class StyleResource extends Resource
])
->emptyStateActions([
Tables\Actions\CreateAction::make(),
])
->persistFiltersInSession()
->persistSortInSession();
]);
}
public static function getRelations(): array

View File

@@ -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';
}
}

View File

@@ -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),
]);
}

View File

@@ -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';
}
}

View File

@@ -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);

View File

@@ -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,
]);
}
}

View File

@@ -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;
},
];

View File

@@ -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);

View File

@@ -16,5 +16,6 @@ class Image extends Model
'original_image_id',
'style_id',
'is_temp',
'is_public',
];
}

View File

@@ -22,6 +22,9 @@ class User extends Authenticatable
'email',
'password',
'role_id',
'email_notifications_enabled',
'theme_preference',
'locale',
];
public function role()

View File

@@ -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'])) {

View File

@@ -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;
}
}

View File

@@ -6,7 +6,6 @@
"license": "MIT",
"require": {
"php": "^8.1",
"filament/filament": "3.0",
"guzzlehttp/guzzle": "^7.2",
"inertiajs/inertia-laravel": "^0.6.8",

253
composer.lock generated
View File

@@ -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",

View File

@@ -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();
});

View File

@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->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']);
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('images', function (Blueprint $table) {
$table->boolean('is_public')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('images', function (Blueprint $table) {
$table->dropColumn('is_public');
});
}
};

View File

@@ -0,0 +1,39 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->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',
]);
});
}
};

71
package-lock.json generated
View File

@@ -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",

View File

@@ -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"
}
}

View File

@@ -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);
});
});

View File

@@ -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;
}

View File

@@ -8,20 +8,31 @@
@click="$emit('imageTapped', image, $event)"
>
<img :src="image.path" :alt="'Image ' + image.name" />
<div v-if="image.is_new" class="new-badge">{{ __('new') }}</div>
</div>
</div>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
const props = defineProps({
images: {
type: Array,
required: true,
},
translations: {
type: Object,
required: true,
},
});
const emits = defineEmits(['imageTapped']);
const __ = (key) => {
return props.translations[key] || key;
};
</script>
<style scoped>
@@ -46,6 +57,7 @@ const emits = defineEmits(['imageTapped']);
overflow: hidden;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
position: relative; /* Added for badge positioning */
}
.grid-item img {
@@ -54,4 +66,17 @@ const emits = defineEmits(['imageTapped']);
object-fit: cover; /* Bilder zuschneiden, um den Bereich zu füllen */
display: block;
}
.new-badge {
position: absolute;
top: 10px;
right: 10px;
background-color: red;
color: white;
padding: 5px 10px;
border-radius: 5px;
font-size: 0.8em;
font-weight: bold;
z-index: 10;
}
</style>

View File

@@ -4,16 +4,45 @@
<div class="context-menu-image-preview">
<img :src="image.path" :alt="'Selected Image ' + image.name" />
</div>
<ul>
<li @click="$emit('print', image)">Drucken</li>
<li @click="$emit('changeStyle', image)">Stil ändern</li>
<li @click="$emit('close')">Schließen</li>
</ul>
<div class="context-menu-options">
<ul v-if="!showStyleSelectorView">
<li @click="$emit('print', image)">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5 inline-block align-middle mr-2">
<path fill-rule="evenodd" d="M7.875 1.5C6.839 1.5 6 2.34 6 3.375v2.25a.75.75 0 0 0 1.5 0V3.375c0-.39.315-.75.75-.75h6.75c.39 0 .75.36.75.75v2.25a.75.75 0 0 0 1.5 0V3.375c0-1.036-.84-1.875-1.875-1.875H7.875Z" clip-rule="evenodd" />
<path fill-rule="evenodd" d="M19.875 9a.75.75 0 0 0-.75-.75H4.875a.75.75 0 0 0-.75.75v6.75c0 1.036.84 1.875 1.875 1.875h11.25c1.036 0 1.875-.84 1.875-1.875V9ZM12 12.75a.75.75 0 0 0 0 1.5h.007a.75.75 0 0 0 0-1.5H12Z" clip-rule="evenodd" />
<path d="M2.25 17.75a.75.75 0 0 0 0 1.5h19.5a.75.75 0 0 0 0-1.5H2.25Z" />
</svg>
Drucken
</li>
<li @click="showStyleSelectorView = true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5 inline-block align-middle mr-2">
<path fill-rule="evenodd" d="M10.788 3.212a.75.75 0 0 0-1.06 0L7.212 5.788a.75.75 0 0 0 0 1.06l1.59 1.59a.75.75 0 0 0 1.06 0l2.576-2.576a.75.75 0 0 0 0-1.06L10.788 3.212ZM15.75 10.5a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM12 12a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5H12a.75.75 0 0 1-.75-.75ZM18.75 10.5a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM12 15a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5H12a.75.75 0 0 1-.75-.75ZM15.75 15a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM18.75 15a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM12 18a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5H12a.75.75 0 0 1-.75-.75ZM15.75 18a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM18.75 18a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM12 21a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5H12a.75.75 0 0 1-.75-.75Z" clip-rule="evenodd" />
</svg>
Stil ändern
</li>
<li @click="$emit('close')">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5 inline-block align-middle mr-2">
<path fill-rule="evenodd" d="M5.47 5.47a.75.75 0 0 1 1.06 0L12 10.94l5.47-5.47a.75.75 0 1 1 1.06 1.06L13.06 12l5.47 5.47a.75.75 0 1 1-1.06 1.06L12 13.06l-5.47 5.47a.75.75 0 0 1-1.06-1.06L10.94 12 5.47 6.53a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
</svg>
Schließen
</li>
</ul>
<StyleSelector
v-else
:image_id="image.image_id"
@styleSelected="(style, imageId) => $emit('styleSelected', style, imageId)"
@back="showStyleSelectorView = false"
@close="$emit('close')"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import StyleSelector from './StyleSelector.vue';
const props = defineProps({
position: {
type: Object,
@@ -25,7 +54,9 @@ const props = defineProps({
},
});
const emits = defineEmits(['close', 'print', 'changeStyle']);
const emits = defineEmits(['close', 'print', 'changeStyle', 'styleSelected']);
const showStyleSelectorView = ref(false);
</script>
<style scoped>
@@ -43,19 +74,19 @@ const emits = defineEmits(['close', 'print', 'changeStyle']);
}
.context-menu {
background: white;
border: 1px solid #ccc;
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
background: var(--color-background);
border: 1px solid var(--color-text);
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.5);
z-index: 1000;
display: flex;
flex-direction: row; /* Bild und Menü nebeneinander */
max-width: 80%;
max-height: 80%;
max-width: 90%;
height: 80vh; /* 80% of viewport height */
overflow: hidden;
}
.context-menu-image-preview {
flex: 1; /* Nimmt den verfügbaren Platz ein */
flex: 7; /* Takes 70% of the space (7 out of 10 parts) */
display: flex;
justify-content: center;
align-items: center;
@@ -65,14 +96,21 @@ const emits = defineEmits(['close', 'print', 'changeStyle']);
.context-menu-image-preview img {
max-width: 100%;
max-height: 100%;
width: 80%; /* 80% width of its container */
object-fit: contain;
}
.context-menu-options {
flex: 3; /* Takes 30% of the space (3 out of 10 parts) */
display: flex;
flex-direction: column;
}
.context-menu ul {
list-style: none;
padding: 0;
margin: 0;
border-left: 1px solid #eee; /* Trennlinie zwischen Bild und Menü */
border-left: 1px solid var(--color-text); /* Trennlinie zwischen Bild und Menü */
min-width: 150px; /* Mindestbreite für das Menü */
}
@@ -80,7 +118,7 @@ const emits = defineEmits(['close', 'print', 'changeStyle']);
padding: 15px;
cursor: pointer;
font-size: 1.1em;
border-bottom: 1px solid #eee;
border-bottom: 1px solid var(--color-text);
}
.context-menu li:last-child {
@@ -88,6 +126,7 @@ const emits = defineEmits(['close', 'print', 'changeStyle']);
}
.context-menu li:hover {
background: #f0f0f0;
background: var(--color-text); /* Adjust hover background for dark mode */
color: var(--color-background); /* Adjust hover text color for dark mode */
}
</style>

View File

@@ -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);
}
</style>

View File

@@ -1,22 +1,24 @@
<template>
<div class="context-menu-overlay" @click.self="$emit('close')">
<div class="style-selector" :style="{ top: `${position.y}px`, left: `${position.x}px` }">
<div class="style-selector-header">
<span class="back-arrow" @click="$emit('back')">&larr;</span>
<h3>Verfügbare Stile</h3>
</div>
<div class="styles-list">
<div
v-for="style in styles"
:key="style.id"
class="style-item"
@click="selectStyle(style)"
>
<img :src="'/storage/' + style.preview_image" :alt="style.title" class="style-thumbnail" />
<div class="style-details">
<h4>{{ style.title }}</h4>
<p>{{ style.description }}</p>
</div>
<div class="style-selector">
<div class="style-selector-header">
<span class="back-arrow" @click="$emit('back')">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
<path fill-rule="evenodd" d="M11.03 4.272a.75.75 0 0 1 0 1.06L6.31 10.5H20.25a.75.75 0 0 1 0 1.5H6.31l4.72 5.168a.75.75 0 0 1-1.06 1.06l-6-6a.75.75 0 0 1 0-1.06l6-6a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd" />
</svg>
</span>
<h3>Verfügbare Stile</h3>
</div>
<div class="styles-list">
<div
v-for="style in styles"
:key="style.id"
class="style-item"
@click="selectStyle(style)"
>
<img :src="'/storage/' + style.preview_image" :alt="style.title" class="style-thumbnail" />
<div class="style-details">
<h4>{{ style.title }}</h4>
<p>{{ style.description }}</p>
</div>
</div>
</div>
@@ -30,10 +32,6 @@ import { ref, onMounted } from 'vue';
const styles = ref([]);
const props = defineProps({
position: {
type: Object,
required: true,
},
image_id: {
type: Number,
required: true,
@@ -63,19 +61,6 @@ onMounted(() => {
</script>
<style scoped>
.context-menu-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5); /* Halbdurchsichtiger Hintergrund */
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
}
.style-selector {
background: white;
border: 1px solid #ccc;
@@ -83,10 +68,9 @@ onMounted(() => {
z-index: 1000;
display: flex;
flex-direction: column;
max-width: 80%;
max-height: 80%;
max-width: 100%; /* Adjusted to fit parent */
max-height: 100%; /* Adjusted to fit parent */
overflow: hidden;
position: absolute; /* Positionierung innerhalb des Overlays */
}
.style-selector-header {
@@ -106,14 +90,16 @@ onMounted(() => {
.back-arrow {
position: absolute;
left: 10px;
font-size: 1.5em;
left: 5px; /* Adjusted position */
font-size: 2em; /* Slightly larger */
cursor: pointer;
padding: 5px;
padding: 10px; /* Larger clickable area */
color: var(--color-text); /* Adapt to theme */
}
.back-arrow:hover {
color: #007bff;
color: var(--color-background); /* Adjust hover color for dark mode */
background: var(--color-text); /* Adjust hover background for dark mode */
}
.styles-list {

View File

@@ -2,8 +2,13 @@
<div class="home">
<div class="main-content">
<div class="gallery-container" @touchstart="handleTouchStart" @touchend="handleTouchEnd">
<h1 class="text-2xl font-bold text-center my-4">Style Gallery</h1>
<GalleryGrid :images="paginatedImages" @imageTapped="showContextMenu" />
<h1 class="text-2xl font-bold text-center my-4">{{ props.galleryHeading }}</h1>
<div class="absolute top-4 right-4">
<button @click="toggleTheme" class="theme-toggle-button">
{{ currentTheme === 'light' ? __('api.dark_mode') : __('api.light_mode') }}
</button>
</div>
<GalleryGrid :images="paginatedImages" @imageTapped="showContextMenu" :translations="props.translations" />
<Navigation
:currentPage="currentPage"
:totalPages="totalPages"
@@ -17,18 +22,10 @@
v-if="currentOverlayComponent === 'contextMenu'"
:position="contextMenuPosition"
:image="selectedImage"
@close="currentOverlayComponent = null"
@close="currentOverlayComponent = null; selectedImage = null"
@print="printImage"
@changeStyle="showStyleSelector"
/>
<StyleSelector
v-if="currentOverlayComponent === 'styleSelector'"
:position="contextMenuPosition"
:image_id="selectedImage.image_id"
@styleSelected="applyStyle"
@back="goBackToContextMenu"
@close="currentOverlayComponent = null"
/>
<div v-if="errorMessage" class="fixed bottom-4 right-4 bg-red-500 text-white p-4 rounded-lg shadow-lg z-50">
@@ -46,38 +43,15 @@
</template>
<script setup>
import Navigation from '../Components/Navigation.vue';
import GalleryGrid from '../Components/GalleryGrid.vue';
import ImageContextMenu from '../Components/ImageContextMenu.vue';
import StyleSelector from '../Components/StyleSelector.vue';
import StyledImageDisplay from '../Components/StyledImageDisplay.vue'; // Import the new component
import LoadingSpinner from '../Components/LoadingSpinner.vue'; // Import the new component
import axios from 'axios';
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { defineProps } from 'vue';
const props = defineProps({
galleryHeading: String,
translations: Object,
});
const images = ref([]);
const imagesPerPage = 12;
const currentPage = ref(1);
const currentOverlayComponent = ref(null); // null, 'contextMenu', 'styleSelector', 'styledImageDisplay'
const contextMenuPosition = ref({ x: 0, y: 0 });
const selectedImage = ref(null);
const styledImage = ref(null); // To store the newly styled image
const errorMessage = ref(null); // New ref for error messages
const isLoading = ref(false); // New ref for loading state
let fetchInterval = null;
let touchStartX = 0;
let touchEndX = 0;
const totalPages = computed(() => {
return Math.ceil(images.value.length / imagesPerPage);
});
const paginatedImages = computed(() => {
const start = (currentPage.value - 1) * imagesPerPage;
const end = start + imagesPerPage;
return images.value.slice(start, end);
});
const fetchImages = () => {
axios.get('/api/images')
@@ -90,6 +64,50 @@ const fetchImages = () => {
});
};
import Navigation from '../Components/Navigation.vue';
import GalleryGrid from '../Components/GalleryGrid.vue';
import ImageContextMenu from '../Components/ImageContextMenu.vue';
import StyleSelector from '../Components/StyleSelector.vue';
import StyledImageDisplay from '../Components/StyledImageDisplay.vue'; // Import the new component
import LoadingSpinner from '../Components/LoadingSpinner.vue'; // Import the new component
import axios from 'axios';
import { ref, computed, onMounted, onUnmounted } from 'vue';
const imagesPerPage = 12;
const currentPage = ref(1);
const currentOverlayComponent = ref(null); // null, 'contextMenu', 'styleSelector', 'styledImageDisplay'
const contextMenuPosition = ref({ x: 0, y: 0 });
const selectedImage = ref(null);
const styledImage = ref(null); // To store the newly styled image
const errorMessage = ref(null); // New ref for error messages
const isLoading = ref(false); // New ref for loading state
const currentTheme = ref('light'); // New ref for current theme
let touchStartX = 0;
let touchEndX = 0;
const applyTheme = (theme) => {
document.documentElement.classList.remove('light', 'dark');
document.documentElement.classList.add(theme);
localStorage.setItem('theme', theme);
currentTheme.value = theme;
};
const toggleTheme = () => {
const newTheme = currentTheme.value === 'light' ? 'dark' : 'light';
applyTheme(newTheme);
};
const totalPages = computed(() => {
return Math.ceil(images.value.length / imagesPerPage);
});
const paginatedImages = computed(() => {
const start = (currentPage.value - 1) * imagesPerPage;
const end = start + imagesPerPage;
return images.value.slice(start, end);
});
const showError = (message) => {
errorMessage.value = message;
setTimeout(() => {
@@ -142,8 +160,6 @@ const applyStyle = (style, imageId) => {
const keepStyledImage = (imageToKeep) => {
console.log('Keeping styled image:', imageToKeep);
// Implement API call to mark image as kept/permanent if needed
// For now, just refresh the image list to show the new image
fetchImages();
currentOverlayComponent.value = null; // Close the display
};
@@ -186,6 +202,16 @@ const handleSwipeGesture = () => {
};
onMounted(() => {
// Apply theme from localStorage on mount
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
applyTheme(savedTheme);
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
applyTheme('dark');
} else {
applyTheme('light');
}
fetchImages();
fetchInterval = setInterval(fetchImages, 5000);
});
@@ -216,4 +242,35 @@ onUnmounted(() => {
align-items: center;
padding: 20px;
}
.theme-toggle-button {
margin-top: 10px;
padding: 8px 15px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 0.9em;
}
.theme-toggle-button:hover {
background-color: #0056b3;
}
.image-visibility-toggle-button {
margin-top: 10px;
padding: 8px 15px;
background-color: #28a745;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 0.9em;
margin-left: 10px;
}
.image-visibility-toggle-button:hover {
background-color: #218838;
}
</style>

View File

@@ -6,15 +6,25 @@ import { createInertiaApp } from '@inertiajs/vue3';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { ZiggyVue } from '../../vendor/tightenco/ziggy';
/* Font Awesome imports */
import { library } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import { faPrint, faMagicWandSparkles, faXmark } from '@fortawesome/free-solid-svg-icons';
// Add icons to the library
library.add(faPrint, faMagicWandSparkles, faXmark);
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
setup({ el, App, props, plugin }) {
console.log('Inertia Page Props (app.js):', props.initialPage.props);
return createApp({ render: () => h(App, props) })
.use(plugin)
.use(ZiggyVue)
.component('font-awesome-icon', FontAwesomeIcon) // Register Font Awesome component
.mixin({
methods: {
__: (key, replace = {}) => {

View File

@@ -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);
});
});

View File

@@ -8,4 +8,10 @@ return [
'image_deleted_successfully' => 'Bild erfolgreich gelöscht.',
'image_or_provider_not_found' => 'Bild oder API-Anbieter nicht gefunden.',
'no_styles_available' => 'Keine Stile oder API-Anbieter aktiviert/verfügbar.',
'api.dark_mode' => 'Dunkler Modus',
'api.light_mode' => 'Heller Modus',
'gallery_title' => 'Eure Bilder aus der Fotobox',
'navigation.previous' => 'Zurück',
'navigation.next' => 'Weiter',
'navigation.page_of' => 'Seite :currentPage von :totalPages',
];

View File

@@ -1,11 +0,0 @@
<?php
return [
'gallery_title' => 'Eure Bilder aus der Fotobox',
'api.image_uploaded_successfully' => 'Bild erfolgreich hochgeladen',
'api.style_or_provider_not_found' => 'Stil oder API-Anbieter nicht gefunden',
'api.image_or_provider_not_found' => 'Bild oder API-Anbieter nicht gefunden',
'navigation.previous' => 'Zurück',
'navigation.next' => 'Weiter',
'navigation.page_of' => 'Seite :currentPage von :totalPages',
];

View File

@@ -1,7 +1,8 @@
<?php
return [
'gallery_heading' => 'Galerie Überschrift',
'saved_successfully' => 'Einstellungen erfolgreich gespeichert.',
'save_button' => 'Speichern',
'new' => 'Neu',
];

View File

@@ -8,4 +8,10 @@ return [
'image_deleted_successfully' => 'Image deleted successfully.',
'image_or_provider_not_found' => 'Image or API provider not found.',
'no_styles_available' => 'No styles or API providers enabled/available.',
'dark_mode' => 'Dark Mode',
'light_mode' => 'Light Mode',
'gallery_title' => 'Your images from the photobooth',
'navigation.previous' => 'Previous',
'navigation.next' => 'Next',
'navigation.page_of' => 'Page :currentPage of :totalPages',
];

View File

@@ -1,11 +0,0 @@
<?php
return [
'gallery_title' => 'Your images from the photobooth',
'api.image_uploaded_successfully' => 'Image uploaded successfully',
'api.style_or_provider_not_found' => 'Style or API Provider not found',
'api.image_or_provider_not_found' => 'Image or API Provider not found',
'navigation.previous' => 'Previous',
'navigation.next' => 'Next',
'navigation.page_of' => 'Page :currentPage of :totalPages',
];

View File

@@ -1,7 +1,8 @@
<?php
return [
'gallery_heading' => 'Gallery Heading',
'saved_successfully' => 'Settings saved successfully.',
'save_button' => 'Save',
'new' => 'New',
];

View File

@@ -16,10 +16,14 @@ use App\Http\Controllers\Api\StyleController;
|
*/
use App\Http\Controllers\Admin\NavigationStateController;
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
Route::post('/admin/navigation-state', [NavigationStateController::class, 'store'])->middleware('auth:sanctum');
// Publicly accessible routes
Route::get('/images', [ImageController::class, 'index']);
Route::get('/styles', [StyleController::class, 'index']);

View File

@@ -30,3 +30,13 @@ Route::middleware('auth')->group(function () {
});
require __DIR__.'/auth.php';
Route::get('/test-translations-en', function () {
app()->setLocale('en');
return response()->json(trans('api'));
});
Route::get('/test-translations-de', function () {
app()->setLocale('de');
return response()->json(trans('api'));
});

245
stack.txt Normal file
View File

@@ -0,0 +1,245 @@
Filament\Forms\Components\Actions\Action:21
getComponent
Filament\Forms\Components\Actions\Action:56
resolveDefaultClosureDependencyForEvaluationByName
Filament\Support\Components\ViewComponent:57
resolveClosureDependencyForEvaluation
Filament\Support\Components\ViewComponent:32
evaluate
Filament\Actions\StaticAction:101
isHidden
Filament\Forms\Components\Actions\ActionContainer:31
isHidden
Filament\Forms\Components\Component:133
isVisible
Filament\Forms\ComponentContainer:114
Filament\Forms\Concerns\{closure}
.unknown0
array_filter
Filament\Forms\ComponentContainer:112
getComponents
C:\wwwroot\stylegallery\backend\vendor\filament\forms\resources\views\components\actions.blade.php:23
include
App\Filament\Resources\UserResource\Pages\EditUser:37
Livewire\Mechanisms\ExtendBlade\{closure}
Livewire\Mechanisms\ExtendBlade\ExtendedCompilerEngine:38
evaluatePath
Illuminate\View\Engines\CompilerEngine:72
get
Livewire\Mechanisms\ExtendBlade\ExtendedCompilerEngine:16
get
Illuminate\View\View:207
getContents
Illuminate\View\View:190
renderContents
Illuminate\View\View:159
render
Filament\Support\Components\ViewComponent:166
toHtml
C:\wwwroot\stylegallery\backend\vendor\laravel\framework\src\Illuminate\Support\helpers.php:117
e
C:\wwwroot\stylegallery\backend\vendor\filament\forms\resources\views\component-container.blade.php:59
include
App\Filament\Resources\UserResource\Pages\EditUser:37
Livewire\Mechanisms\ExtendBlade\{closure}
Livewire\Mechanisms\ExtendBlade\ExtendedCompilerEngine:38
evaluatePath
Illuminate\View\Engines\CompilerEngine:72
get
Livewire\Mechanisms\ExtendBlade\ExtendedCompilerEngine:16
get
Illuminate\View\View:207
getContents
Illuminate\View\View:190
renderContents
Illuminate\View\View:159
render
Filament\Support\Components\ViewComponent:166
toHtml
C:\wwwroot\stylegallery\backend\vendor\laravel\framework\src\Illuminate\Support\helpers.php:117
e
C:\wwwroot\stylegallery\backend\vendor\filament\forms\resources\views\components\section.blade.php:25
include
App\Filament\Resources\UserResource\Pages\EditUser:37
Livewire\Mechanisms\ExtendBlade\{closure}
Livewire\Mechanisms\ExtendBlade\ExtendedCompilerEngine:38
evaluatePath
Illuminate\View\Engines\CompilerEngine:72
get
Livewire\Mechanisms\ExtendBlade\ExtendedCompilerEngine:16
get
Illuminate\View\View:207
getContents
Illuminate\View\View:190
renderContents
Illuminate\View\View:159
render
Filament\Support\Components\ViewComponent:166
toHtml
C:\wwwroot\stylegallery\backend\vendor\laravel\framework\src\Illuminate\Support\helpers.php:117
e
C:\wwwroot\stylegallery\backend\vendor\filament\forms\resources\views\component-container.blade.php:59
include
App\Filament\Resources\UserResource\Pages\EditUser:37
Livewire\Mechanisms\ExtendBlade\{closure}
Livewire\Mechanisms\ExtendBlade\ExtendedCompilerEngine:38
evaluatePath
Illuminate\View\Engines\CompilerEngine:72
get
Livewire\Mechanisms\ExtendBlade\ExtendedCompilerEngine:16
get
Illuminate\View\View:207
getContents
Illuminate\View\View:190
renderContents
Illuminate\View\View:159
render
Filament\Support\Components\ViewComponent:166
toHtml
C:\wwwroot\stylegallery\backend\vendor\laravel\framework\src\Illuminate\Support\helpers.php:117
e
App\Filament\Resources\UserResource\Pages\EditUser:15
{closure}
C:\wwwroot\stylegallery\backend\vendor\filament\filament\resources\views\resources\pages\edit-record.blade.php:31
include
App\Filament\Resources\UserResource\Pages\EditUser:37
Livewire\Mechanisms\ExtendBlade\{closure}
Livewire\Mechanisms\ExtendBlade\ExtendedCompilerEngine:38
evaluatePath
Illuminate\View\Engines\CompilerEngine:72
get
Livewire\Mechanisms\ExtendBlade\ExtendedCompilerEngine:16
get
Illuminate\View\View:207
getContents
Illuminate\View\View:190
renderContents
Illuminate\View\View:159
render
Livewire\Mechanisms\HandleComponents\HandleComponents:259
Livewire\Mechanisms\HandleComponents\{closure}
Livewire\Mechanisms\HandleComponents\HandleComponents:303
trackInRenderStack
Livewire\Mechanisms\HandleComponents\HandleComponents:251
render
Livewire\Mechanisms\HandleComponents\HandleComponents:54
mount
Livewire\LivewireManager:73
mount
Livewire\Component:17
Livewire\Features\SupportPageComponents\{closure}
Livewire\Features\SupportPageComponents\SupportPageComponents:117
interceptTheRenderOfTheComponentAndRetreiveTheLayoutConfiguration
Livewire\Component:14
__invoke
Illuminate\Routing\ControllerDispatcher:46
dispatch
Illuminate\Routing\Route:259
runController
Illuminate\Routing\Route:205
run
Illuminate\Routing\Router:806
Illuminate\Routing\{closure}
Illuminate\Pipeline\Pipeline:144
Illuminate\Pipeline\{closure}
Filament\Http\Middleware\DispatchServingFilamentEvent:15
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Filament\Http\Middleware\DisableBladeIconComponents:14
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Illuminate\Routing\Middleware\SubstituteBindings:50
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Illuminate\Foundation\Http\Middleware\VerifyCsrfToken:78
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Illuminate\Session\Middleware\AuthenticateSession:60
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Illuminate\Auth\Middleware\Authenticate:57
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Illuminate\View\Middleware\ShareErrorsFromSession:49
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Illuminate\Session\Middleware\StartSession:121
handleStatefulRequest
Illuminate\Session\Middleware\StartSession:64
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse:37
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Illuminate\Cookie\Middleware\EncryptCookies:67
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Filament\Http\Middleware\SetUpPanel:19
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Illuminate\Pipeline\Pipeline:119
then
Illuminate\Routing\Router:805
runRouteWithinStack
Illuminate\Routing\Router:784
runRoute
Illuminate\Routing\Router:748
dispatchToRoute
Illuminate\Routing\Router:737
dispatch
Illuminate\Foundation\Http\Kernel:200
Illuminate\Foundation\Http\{closure}
Illuminate\Pipeline\Pipeline:144
Illuminate\Pipeline\{closure}
Livewire\Features\SupportDisablingBackButtonCache\DisableBackButtonCacheMiddleware:19
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Illuminate\Foundation\Http\Middleware\TransformsRequest:21
handle
Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull:31
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Illuminate\Foundation\Http\Middleware\TransformsRequest:21
handle
Illuminate\Foundation\Http\Middleware\TrimStrings:40
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Illuminate\Foundation\Http\Middleware\ValidatePostSize:27
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance:99
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Illuminate\Http\Middleware\HandleCors:62
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Illuminate\Http\Middleware\TrustProxies:39
handle
Illuminate\Pipeline\Pipeline:183
Illuminate\Pipeline\{closure}
Illuminate\Pipeline\Pipeline:119
then
Illuminate\Foundation\Http\Kernel:175
sendRequestThroughRouter
Illuminate\Foundation\Http\Kernel:144
handle
C:\wwwroot\stylegallery\backend\public\index.php:51
require_once
C:\wwwroot\stylegallery\backend\vendor\laravel\framework\src\Illuminate\Foundation\resources\server.php:16