mirror of
https://github.com/openclassify/openclassify.git
synced 2026-04-14 03:02:08 -05:00
feat: Install FilamentPHP v5, add Admin/Partner modules, Docker support
- Fix CI workflow: actions/checkout@v6 → v4
- Install FilamentPHP ^5.0 and spatie/laravel-permission ^7.2
- Remove laravel/breeze from require-dev (replaced by Filament auth)
- Update User model: implements FilamentUser, HasTenants, HasRoles
- Publish Spatie permission migrations for role-based access
- Create Admin module with FilamentPHP panel at /admin
- UserResource, CategoryResource, ListingResource, LocationResource
- Role-based access (admin role required)
- Create Partner module with tenant-isolated panel at /partner/{id}
- ListingResource scoped to authenticated user
- Update modules_statuses.json: add Admin and Partner modules
- Update DatabaseSeeder: create admin/partner users with roles
- Improve seeders: LocationSeeder (10 countries), CategorySeeder (8 categories with children), ListingSeeder (10 sample listings)
- Add Docker support: Dockerfile, docker-compose.yml, docker-compose.dev.yml
- Add GitHub Codespaces: .devcontainer/devcontainer.json
- Update .env.example with Filament-relevant settings
- Update README.md with full documentation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
268f736147
commit
64930897c3
33
.devcontainer/devcontainer.json
Normal file
33
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "OpenClassify Dev",
|
||||
"dockerComposeFile": ["../docker-compose.dev.yml"],
|
||||
"service": "app",
|
||||
"workspaceFolder": "/var/www/html",
|
||||
"forwardPorts": [8000, 5173, 8025],
|
||||
"portsAttributes": {
|
||||
"8000": {"label": "Application", "onAutoForward": "openPreview"},
|
||||
"5173": {"label": "Vite HMR"},
|
||||
"8025": {"label": "Mailpit"}
|
||||
},
|
||||
"postCreateCommand": "composer install && npm install && cp -n .env.example .env && php artisan key:generate --force",
|
||||
"postStartCommand": "php artisan migrate --force && php artisan db:seed --force",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"bmewburn.vscode-intelephense-client",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"esbenp.prettier-vscode",
|
||||
"onecentlin.laravel-blade",
|
||||
"amiralizadeh9480.laravel-extra-intellisense"
|
||||
],
|
||||
"settings": {
|
||||
"php.validate.executablePath": "/usr/local/bin/php",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[php]": {
|
||||
"editor.defaultFormatter": "bmewburn.vscode-intelephense-client"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
.env.example
24
.env.example
@ -1,17 +1,14 @@
|
||||
APP_NAME=Laravel
|
||||
APP_NAME=OpenClassify
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
APP_URL=http://localhost:8000
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
# APP_MAINTENANCE_STORE=database
|
||||
|
||||
# PHP_CLI_SERVER_WORKERS=4
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
@ -23,9 +20,9 @@ LOG_LEVEL=debug
|
||||
DB_CONNECTION=sqlite
|
||||
# DB_HOST=127.0.0.1
|
||||
# DB_PORT=3306
|
||||
# DB_DATABASE=laravel
|
||||
# DB_USERNAME=root
|
||||
# DB_PASSWORD=
|
||||
# DB_DATABASE=openclassify
|
||||
# DB_USERNAME=openclassify
|
||||
# DB_PASSWORD=secret
|
||||
|
||||
SESSION_DRIVER=database
|
||||
SESSION_LIFETIME=120
|
||||
@ -38,9 +35,6 @@ FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=database
|
||||
|
||||
CACHE_STORE=database
|
||||
# CACHE_PREFIX=
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=127.0.0.1
|
||||
@ -53,13 +47,7 @@ MAIL_HOST=127.0.0.1
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_ADDRESS="hello@openclassify.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
|
||||
41
Dockerfile
Normal file
41
Dockerfile
Normal file
@ -0,0 +1,41 @@
|
||||
FROM php:8.3-fpm-alpine
|
||||
|
||||
RUN apk add --no-cache \
|
||||
nginx \
|
||||
nodejs \
|
||||
npm \
|
||||
git \
|
||||
curl \
|
||||
zip \
|
||||
unzip \
|
||||
libpng-dev \
|
||||
libjpeg-turbo-dev \
|
||||
freetype-dev \
|
||||
oniguruma-dev \
|
||||
libxml2-dev \
|
||||
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
|
||||
&& docker-php-ext-install pdo pdo_mysql mbstring exif pcntl bcmath gd opcache
|
||||
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
COPY composer.json composer.lock ./
|
||||
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN composer dump-autoload --optimize \
|
||||
&& npm ci \
|
||||
&& npm run build
|
||||
|
||||
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY docker/start.sh /start.sh
|
||||
RUN chmod +x /start.sh
|
||||
|
||||
RUN chown -R www-data:www-data storage bootstrap/cache \
|
||||
&& chmod -R 775 storage bootstrap/cache
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["/start.sh"]
|
||||
29
Dockerfile.dev
Normal file
29
Dockerfile.dev
Normal file
@ -0,0 +1,29 @@
|
||||
FROM php:8.3-fpm-alpine
|
||||
|
||||
RUN apk add --no-cache \
|
||||
nginx \
|
||||
nodejs \
|
||||
npm \
|
||||
git \
|
||||
curl \
|
||||
zip \
|
||||
unzip \
|
||||
libpng-dev \
|
||||
libjpeg-turbo-dev \
|
||||
freetype-dev \
|
||||
oniguruma-dev \
|
||||
libxml2-dev \
|
||||
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
|
||||
&& docker-php-ext-install pdo pdo_mysql mbstring exif pcntl bcmath gd
|
||||
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY docker/start-dev.sh /start.sh
|
||||
RUN chmod +x /start.sh
|
||||
|
||||
EXPOSE 80 5173
|
||||
|
||||
CMD ["/start.sh"]
|
||||
56
Modules/Admin/Filament/Resources/CategoryResource.php
Normal file
56
Modules/Admin/Filament/Resources/CategoryResource.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Filament\Resources;
|
||||
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Modules\Admin\Filament\Resources\CategoryResource\Pages;
|
||||
use Modules\Category\Models\Category;
|
||||
|
||||
class CategoryResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Category::class;
|
||||
protected static ?string $navigationIcon = 'heroicon-o-tag';
|
||||
protected static ?string $navigationGroup = 'Catalog';
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form->schema([
|
||||
TextInput::make('name')->required()->maxLength(255)->live(onBlur: true)->afterStateUpdated(fn ($state, $set) => $set('slug', \Illuminate\Support\Str::slug($state))),
|
||||
TextInput::make('slug')->required()->maxLength(255)->unique(ignoreRecord: true),
|
||||
TextInput::make('description')->maxLength(500),
|
||||
TextInput::make('icon')->maxLength(100),
|
||||
Select::make('parent_id')->label('Parent Category')->options(fn () => Category::whereNull('parent_id')->pluck('name', 'id'))->nullable()->searchable(),
|
||||
TextInput::make('sort_order')->numeric()->default(0),
|
||||
Toggle::make('is_active')->default(true),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table->columns([
|
||||
TextColumn::make('id')->sortable(),
|
||||
TextColumn::make('name')->searchable()->sortable(),
|
||||
TextColumn::make('parent.name')->label('Parent')->default('-'),
|
||||
TextColumn::make('listings_count')->counts('listings')->label('Listings'),
|
||||
IconColumn::make('is_active')->boolean(),
|
||||
TextColumn::make('sort_order')->sortable(),
|
||||
])->actions([EditAction::make(), DeleteAction::make()]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListCategories::route('/'),
|
||||
'create' => Pages\CreateCategory::route('/create'),
|
||||
'edit' => Pages\EditCategory::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Filament\Resources\CategoryResource\Pages;
|
||||
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Modules\Admin\Filament\Resources\CategoryResource;
|
||||
|
||||
class CreateCategory extends CreateRecord
|
||||
{
|
||||
protected static string $resource = CategoryResource::class;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Filament\Resources\CategoryResource\Pages;
|
||||
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Modules\Admin\Filament\Resources\CategoryResource;
|
||||
|
||||
class EditCategory extends EditRecord
|
||||
{
|
||||
protected static string $resource = CategoryResource::class;
|
||||
protected function getHeaderActions(): array { return [DeleteAction::make()]; }
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Filament\Resources\CategoryResource\Pages;
|
||||
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Modules\Admin\Filament\Resources\CategoryResource;
|
||||
|
||||
class ListCategories extends ListRecords
|
||||
{
|
||||
protected static string $resource = CategoryResource::class;
|
||||
protected function getHeaderActions(): array { return [CreateAction::make()]; }
|
||||
}
|
||||
68
Modules/Admin/Filament/Resources/ListingResource.php
Normal file
68
Modules/Admin/Filament/Resources/ListingResource.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Filament\Resources;
|
||||
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Modules\Admin\Filament\Resources\ListingResource\Pages;
|
||||
use Modules\Category\Models\Category;
|
||||
use Modules\Listing\Models\Listing;
|
||||
|
||||
class ListingResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Listing::class;
|
||||
protected static ?string $navigationIcon = 'heroicon-o-clipboard-document-list';
|
||||
protected static ?string $navigationGroup = 'Catalog';
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form->schema([
|
||||
TextInput::make('title')->required()->maxLength(255)->live(onBlur: true)->afterStateUpdated(fn ($state, $set) => $set('slug', \Illuminate\Support\Str::slug($state) . '-' . \Illuminate\Support\Str::random(4))),
|
||||
TextInput::make('slug')->required()->maxLength(255)->unique(ignoreRecord: true),
|
||||
Textarea::make('description')->rows(4),
|
||||
TextInput::make('price')->numeric()->prefix('$'),
|
||||
TextInput::make('currency')->default('USD')->maxLength(3),
|
||||
Select::make('category_id')->label('Category')->options(fn () => Category::where('is_active', true)->pluck('name', 'id'))->searchable()->nullable(),
|
||||
Select::make('status')->options(['active' => 'Active', 'pending' => 'Pending', 'sold' => 'Sold', 'expired' => 'Expired'])->default('active')->required(),
|
||||
TextInput::make('contact_phone')->tel()->maxLength(50),
|
||||
TextInput::make('contact_email')->email()->maxLength(255),
|
||||
Toggle::make('is_featured')->default(false),
|
||||
TextInput::make('city')->maxLength(100),
|
||||
TextInput::make('country')->maxLength(100),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table->columns([
|
||||
TextColumn::make('id')->sortable(),
|
||||
TextColumn::make('title')->searchable()->sortable()->limit(40),
|
||||
TextColumn::make('category.name')->label('Category'),
|
||||
TextColumn::make('price')->money('USD')->sortable(),
|
||||
TextColumn::make('status')->badge()->color(fn ($state) => match ($state) { 'active' => 'success', 'sold' => 'gray', 'pending' => 'warning', default => 'danger' }),
|
||||
IconColumn::make('is_featured')->boolean()->label('Featured'),
|
||||
TextColumn::make('city'),
|
||||
TextColumn::make('created_at')->dateTime()->sortable(),
|
||||
])->filters([
|
||||
SelectFilter::make('status')->options(['active' => 'Active', 'pending' => 'Pending', 'sold' => 'Sold', 'expired' => 'Expired']),
|
||||
])->actions([EditAction::make(), DeleteAction::make()]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListListings::route('/'),
|
||||
'create' => Pages\CreateListing::route('/create'),
|
||||
'edit' => Pages\EditListing::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Filament\Resources\ListingResource\Pages;
|
||||
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Modules\Admin\Filament\Resources\ListingResource;
|
||||
|
||||
class CreateListing extends CreateRecord
|
||||
{
|
||||
protected static string $resource = ListingResource::class;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Filament\Resources\ListingResource\Pages;
|
||||
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Modules\Admin\Filament\Resources\ListingResource;
|
||||
|
||||
class EditListing extends EditRecord
|
||||
{
|
||||
protected static string $resource = ListingResource::class;
|
||||
protected function getHeaderActions(): array { return [DeleteAction::make()]; }
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Filament\Resources\ListingResource\Pages;
|
||||
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Modules\Admin\Filament\Resources\ListingResource;
|
||||
|
||||
class ListListings extends ListRecords
|
||||
{
|
||||
protected static string $resource = ListingResource::class;
|
||||
protected function getHeaderActions(): array { return [CreateAction::make()]; }
|
||||
}
|
||||
52
Modules/Admin/Filament/Resources/LocationResource.php
Normal file
52
Modules/Admin/Filament/Resources/LocationResource.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Filament\Resources;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Modules\Admin\Filament\Resources\LocationResource\Pages;
|
||||
use Modules\Location\Models\Country;
|
||||
|
||||
class LocationResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Country::class;
|
||||
protected static ?string $navigationIcon = 'heroicon-o-globe-alt';
|
||||
protected static ?string $navigationGroup = 'Settings';
|
||||
protected static ?string $label = 'Country';
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form->schema([
|
||||
TextInput::make('name')->required()->maxLength(100),
|
||||
TextInput::make('code')->required()->maxLength(2)->unique(ignoreRecord: true),
|
||||
TextInput::make('phone_code')->maxLength(10),
|
||||
Toggle::make('is_active')->default(true),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table->columns([
|
||||
TextColumn::make('id')->sortable(),
|
||||
TextColumn::make('name')->searchable()->sortable(),
|
||||
TextColumn::make('code'),
|
||||
TextColumn::make('phone_code'),
|
||||
IconColumn::make('is_active')->boolean(),
|
||||
])->actions([EditAction::make(), DeleteAction::make()]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListLocations::route('/'),
|
||||
'create' => Pages\CreateLocation::route('/create'),
|
||||
'edit' => Pages\EditLocation::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Filament\Resources\LocationResource\Pages;
|
||||
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Modules\Admin\Filament\Resources\LocationResource;
|
||||
|
||||
class CreateLocation extends CreateRecord
|
||||
{
|
||||
protected static string $resource = LocationResource::class;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Filament\Resources\LocationResource\Pages;
|
||||
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Modules\Admin\Filament\Resources\LocationResource;
|
||||
|
||||
class EditLocation extends EditRecord
|
||||
{
|
||||
protected static string $resource = LocationResource::class;
|
||||
protected function getHeaderActions(): array { return [DeleteAction::make()]; }
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Filament\Resources\LocationResource\Pages;
|
||||
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Modules\Admin\Filament\Resources\LocationResource;
|
||||
|
||||
class ListLocations extends ListRecords
|
||||
{
|
||||
protected static string $resource = LocationResource::class;
|
||||
protected function getHeaderActions(): array { return [CreateAction::make()]; }
|
||||
}
|
||||
50
Modules/Admin/Filament/Resources/UserResource.php
Normal file
50
Modules/Admin/Filament/Resources/UserResource.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Filament\Resources;
|
||||
|
||||
use App\Models\User;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Modules\Admin\Filament\Resources\UserResource\Pages;
|
||||
|
||||
class UserResource extends Resource
|
||||
{
|
||||
protected static ?string $model = User::class;
|
||||
protected static ?string $navigationIcon = 'heroicon-o-users';
|
||||
protected static ?string $navigationGroup = 'User Management';
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form->schema([
|
||||
TextInput::make('name')->required()->maxLength(255),
|
||||
TextInput::make('email')->email()->required()->maxLength(255)->unique(ignoreRecord: true),
|
||||
TextInput::make('password')->password()->required(fn ($livewire) => $livewire instanceof Pages\CreateUser)->dehydrateStateUsing(fn ($state) => filled($state) ? bcrypt($state) : null)->dehydrated(fn ($state) => filled($state)),
|
||||
Select::make('roles')->multiple()->relationship('roles', 'name')->preload(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table->columns([
|
||||
TextColumn::make('id')->sortable(),
|
||||
TextColumn::make('name')->searchable()->sortable(),
|
||||
TextColumn::make('email')->searchable()->sortable(),
|
||||
TextColumn::make('roles.name')->badge()->label('Roles'),
|
||||
TextColumn::make('created_at')->dateTime()->sortable(),
|
||||
])->actions([EditAction::make(), DeleteAction::make()]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListUsers::route('/'),
|
||||
'create' => Pages\CreateUser::route('/create'),
|
||||
'edit' => Pages\EditUser::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Modules\Admin\Filament\Resources\UserResource;
|
||||
|
||||
class CreateUser extends CreateRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Modules\Admin\Filament\Resources\UserResource;
|
||||
|
||||
class EditUser extends EditRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
protected function getHeaderActions(): array { return [DeleteAction::make()]; }
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Modules\Admin\Filament\Resources\UserResource;
|
||||
|
||||
class ListUsers extends ListRecords
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
protected function getHeaderActions(): array { return [CreateAction::make()]; }
|
||||
}
|
||||
49
Modules/Admin/Providers/AdminPanelProvider.php
Normal file
49
Modules/Admin/Providers/AdminPanelProvider.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Providers;
|
||||
|
||||
use Filament\Http\Middleware\Authenticate;
|
||||
use Filament\Http\Middleware\AuthenticateSession;
|
||||
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||
use Filament\Pages\Dashboard;
|
||||
use Filament\Panel;
|
||||
use Filament\PanelProvider;
|
||||
use Filament\Support\Colors\Color;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
use Modules\Admin\Filament\Resources\CategoryResource;
|
||||
use Modules\Admin\Filament\Resources\ListingResource;
|
||||
use Modules\Admin\Filament\Resources\LocationResource;
|
||||
use Modules\Admin\Filament\Resources\UserResource;
|
||||
|
||||
class AdminPanelProvider extends PanelProvider
|
||||
{
|
||||
public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
->id('admin')
|
||||
->path('admin')
|
||||
->login()
|
||||
->colors(['primary' => Color::Blue])
|
||||
->discoverResources(in: module_path('Admin', 'Filament/Resources'), for: 'Modules\\Admin\\Filament\\Resources')
|
||||
->discoverPages(in: module_path('Admin', 'Filament/Pages'), for: 'Modules\\Admin\\Filament\\Pages')
|
||||
->discoverWidgets(in: module_path('Admin', 'Filament/Widgets'), for: 'Modules\\Admin\\Filament\\Widgets')
|
||||
->pages([Dashboard::class])
|
||||
->middleware([
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartSession::class,
|
||||
AuthenticateSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
DisableBladeIconComponents::class,
|
||||
DispatchServingFilamentEvent::class,
|
||||
])
|
||||
->authMiddleware([Authenticate::class]);
|
||||
}
|
||||
}
|
||||
17
Modules/Admin/Providers/AdminServiceProvider.php
Normal file
17
Modules/Admin/Providers/AdminServiceProvider.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AdminServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function boot(): void
|
||||
{
|
||||
$this->loadMigrationsFrom(module_path('Admin', 'database/migrations'));
|
||||
}
|
||||
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->register(AdminPanelProvider::class);
|
||||
}
|
||||
}
|
||||
13
Modules/Admin/module.json
Normal file
13
Modules/Admin/module.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "Admin",
|
||||
"alias": "admin",
|
||||
"description": "Admin panel using FilamentPHP",
|
||||
"keywords": [],
|
||||
"priority": 0,
|
||||
"providers": [
|
||||
"Modules\\Admin\\Providers\\AdminServiceProvider"
|
||||
],
|
||||
"aliases": {},
|
||||
"files": [],
|
||||
"requires": []
|
||||
}
|
||||
@ -3,32 +3,33 @@ namespace Modules\Category\Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Modules\Category\Models\Category;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CategorySeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$categories = [
|
||||
['name' => 'Electronics', 'icon' => '📱', 'children' => ['Mobile Phones', 'Laptops & Computers', 'Tablets', 'Cameras', 'Audio']],
|
||||
['name' => 'Vehicles', 'icon' => '🚗', 'children' => ['Cars', 'Motorcycles', 'Trucks', 'Boats']],
|
||||
['name' => 'Real Estate', 'icon' => '🏠', 'children' => ['Apartments for Rent', 'Houses for Sale', 'Commercial', 'Land']],
|
||||
['name' => 'Furniture', 'icon' => '🛋️', 'children' => ['Sofas', 'Beds', 'Tables', 'Wardrobes']],
|
||||
['name' => 'Fashion', 'icon' => '👗', 'children' => ['Women', 'Men', 'Kids', 'Accessories']],
|
||||
['name' => 'Jobs', 'icon' => '💼', 'children' => ['IT & Technology', 'Marketing', 'Sales', 'Education']],
|
||||
['name' => 'Services', 'icon' => '🔧', 'children' => ['Home Repair', 'Tutoring', 'Design', 'Cleaning']],
|
||||
['name' => 'Sports & Hobbies', 'icon' => '⚽', 'children' => ['Sports Equipment', 'Musical Instruments', 'Books', 'Games']],
|
||||
['name' => 'Electronics', 'slug' => 'electronics', 'icon' => 'laptop', 'children' => ['Phones', 'Computers', 'Tablets', 'TVs']],
|
||||
['name' => 'Vehicles', 'slug' => 'vehicles', 'icon' => 'car', 'children' => ['Cars', 'Motorcycles', 'Trucks', 'Boats']],
|
||||
['name' => 'Real Estate', 'slug' => 'real-estate', 'icon' => 'home', 'children' => ['For Sale', 'For Rent', 'Commercial']],
|
||||
['name' => 'Fashion', 'slug' => 'fashion', 'icon' => 'shirt', 'children' => ['Men', 'Women', 'Kids', 'Shoes']],
|
||||
['name' => 'Home & Garden', 'slug' => 'home-garden', 'icon' => 'sofa', 'children' => ['Furniture', 'Garden', 'Appliances']],
|
||||
['name' => 'Sports', 'slug' => 'sports', 'icon' => 'football', 'children' => ['Outdoor', 'Fitness', 'Team Sports']],
|
||||
['name' => 'Jobs', 'slug' => 'jobs', 'icon' => 'briefcase', 'children' => ['Full Time', 'Part Time', 'Freelance']],
|
||||
['name' => 'Services', 'slug' => 'services', 'icon' => 'wrench', 'children' => ['Cleaning', 'Repair', 'Education']],
|
||||
];
|
||||
|
||||
foreach ($categories as $catData) {
|
||||
foreach ($categories as $index => $data) {
|
||||
$parent = Category::firstOrCreate(
|
||||
['slug' => Str::slug($catData['name'])],
|
||||
['name' => $catData['name'], 'icon' => $catData['icon'] ?? null, 'level' => 0, 'is_active' => true]
|
||||
['slug' => $data['slug']],
|
||||
['name' => $data['name'], 'slug' => $data['slug'], 'icon' => $data['icon'], 'level' => 0, 'sort_order' => $index, 'is_active' => true]
|
||||
);
|
||||
foreach ($catData['children'] as $childName) {
|
||||
|
||||
foreach ($data['children'] as $i => $childName) {
|
||||
$childSlug = $data['slug'] . '-' . \Illuminate\Support\Str::slug($childName);
|
||||
Category::firstOrCreate(
|
||||
['slug' => Str::slug($childName)],
|
||||
['name' => $childName, 'parent_id' => $parent->id, 'level' => 1, 'is_active' => true]
|
||||
['slug' => $childSlug],
|
||||
['name' => $childName, 'slug' => $childSlug, 'parent_id' => $parent->id, 'level' => 1, 'sort_order' => $i, 'is_active' => true]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,29 +2,47 @@
|
||||
namespace Modules\Listing\Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Modules\Category\Models\Category;
|
||||
use Modules\Listing\Models\Listing;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ListingSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$user = \App\Models\User::where('email', 'partner@openclassify.com')->first();
|
||||
$categories = Category::where('level', 0)->get();
|
||||
|
||||
if (!$user || $categories->isEmpty()) return;
|
||||
|
||||
$listings = [
|
||||
['title' => 'iPhone 14 Pro - Like New', 'price' => 750, 'category_id' => 1, 'status' => 'active'],
|
||||
['title' => 'Samsung Galaxy S23', 'price' => 550, 'category_id' => 1, 'status' => 'active'],
|
||||
['title' => 'MacBook Pro 2023', 'price' => 1800, 'category_id' => 2, 'status' => 'active'],
|
||||
['title' => '2019 Toyota Corolla', 'price' => 15000, 'category_id' => 3, 'status' => 'active'],
|
||||
['title' => 'Apartment for Rent - 2BR', 'price' => 1200, 'category_id' => 4, 'status' => 'active'],
|
||||
['title' => 'Sofa Set - Excellent Condition', 'price' => 350, 'category_id' => 5, 'status' => 'active'],
|
||||
['title' => 'iPhone 14 Pro - Excellent Condition', 'price' => 799, 'city' => 'Istanbul', 'country' => 'Turkey'],
|
||||
['title' => 'MacBook Pro 2023', 'price' => 1499, 'city' => 'Ankara', 'country' => 'Turkey'],
|
||||
['title' => '2020 Toyota Corolla', 'price' => 18000, 'city' => 'New York', 'country' => 'United States'],
|
||||
['title' => '3-Bedroom Apartment for Sale', 'price' => 250000, 'city' => 'Istanbul', 'country' => 'Turkey'],
|
||||
['title' => 'Nike Running Shoes Size 42', 'price' => 89, 'city' => 'Berlin', 'country' => 'Germany'],
|
||||
['title' => 'IKEA Dining Table', 'price' => 150, 'city' => 'London', 'country' => 'United Kingdom'],
|
||||
['title' => 'Yoga Mat - Brand New', 'price' => 35, 'city' => 'Paris', 'country' => 'France'],
|
||||
['title' => 'Web Developer for Hire', 'price' => 0, 'city' => 'Remote', 'country' => 'Turkey'],
|
||||
['title' => 'Samsung 55" 4K TV', 'price' => 599, 'city' => 'Madrid', 'country' => 'Spain'],
|
||||
['title' => 'Honda CBR500R Motorcycle 2021', 'price' => 6500, 'city' => 'Tokyo', 'country' => 'Japan'],
|
||||
];
|
||||
|
||||
foreach ($listings as $data) {
|
||||
$data['slug'] = Str::slug($data['title']) . '-' . Str::random(6);
|
||||
$data['description'] = 'Great item in excellent condition. Contact for more details.';
|
||||
$data['contact_email'] = 'seller@example.com';
|
||||
$data['city'] = 'Istanbul';
|
||||
$data['country'] = 'Turkey';
|
||||
Listing::firstOrCreate(['title' => $data['title']], $data);
|
||||
foreach ($listings as $i => $listing) {
|
||||
$category = $categories->get($i % $categories->count());
|
||||
Listing::firstOrCreate(
|
||||
['slug' => \Illuminate\Support\Str::slug($listing['title']) . '-' . ($i + 1)],
|
||||
array_merge($listing, [
|
||||
'slug' => \Illuminate\Support\Str::slug($listing['title']) . '-' . ($i + 1),
|
||||
'description' => 'This is a sample listing description for ' . $listing['title'],
|
||||
'currency' => $listing['price'] > 0 ? 'USD' : 'USD',
|
||||
'category_id' => $category?->id,
|
||||
'user_id' => $user->id,
|
||||
'status' => 'active',
|
||||
'contact_email' => 'partner@openclassify.com',
|
||||
'contact_phone' => '+1234567890',
|
||||
'is_featured' => $i < 3,
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,36 +2,43 @@
|
||||
namespace Modules\Location\Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Modules\Location\Models\Country;
|
||||
use Modules\Location\Models\City;
|
||||
use Modules\Location\Models\District;
|
||||
use Modules\Location\Models\Country;
|
||||
|
||||
class LocationSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$locations = [
|
||||
['name' => 'Turkey', 'code' => 'TR', 'phone_code' => '+90', 'flag' => '🇹🇷',
|
||||
'cities' => ['Istanbul' => ['Beyoglu', 'Kadikoy', 'Besiktas'], 'Ankara' => ['Cankaya', 'Kecioren'], 'Izmir' => ['Konak', 'Karsiyaka']]],
|
||||
['name' => 'United States', 'code' => 'US', 'phone_code' => '+1', 'flag' => '🇺🇸',
|
||||
'cities' => ['New York' => ['Manhattan', 'Brooklyn'], 'Los Angeles' => ['Hollywood', 'Venice'], 'Chicago' => ['Downtown', 'Midtown']]],
|
||||
['name' => 'United Kingdom', 'code' => 'GB', 'phone_code' => '+44', 'flag' => '🇬🇧',
|
||||
'cities' => ['London' => ['Westminster', 'Shoreditch'], 'Manchester' => ['City Centre'], 'Birmingham' => ['Jewellery Quarter']]],
|
||||
['name' => 'Germany', 'code' => 'DE', 'phone_code' => '+49', 'flag' => '🇩🇪',
|
||||
'cities' => ['Berlin' => ['Mitte', 'Prenzlauer Berg'], 'Munich' => ['Schwabing', 'Maxvorstadt']]],
|
||||
['name' => 'France', 'code' => 'FR', 'phone_code' => '+33', 'flag' => '🇫🇷',
|
||||
'cities' => ['Paris' => ['Marais', 'Montmartre'], 'Lyon' => ['Presquile']]],
|
||||
$countries = [
|
||||
['name' => 'Turkey', 'code' => 'TR', 'phone_code' => '+90'],
|
||||
['name' => 'United States', 'code' => 'US', 'phone_code' => '+1'],
|
||||
['name' => 'Germany', 'code' => 'DE', 'phone_code' => '+49'],
|
||||
['name' => 'France', 'code' => 'FR', 'phone_code' => '+33'],
|
||||
['name' => 'United Kingdom', 'code' => 'GB', 'phone_code' => '+44'],
|
||||
['name' => 'Spain', 'code' => 'ES', 'phone_code' => '+34'],
|
||||
['name' => 'Italy', 'code' => 'IT', 'phone_code' => '+39'],
|
||||
['name' => 'Russia', 'code' => 'RU', 'phone_code' => '+7'],
|
||||
['name' => 'China', 'code' => 'CN', 'phone_code' => '+86'],
|
||||
['name' => 'Japan', 'code' => 'JP', 'phone_code' => '+81'],
|
||||
];
|
||||
|
||||
foreach ($locations as $countryData) {
|
||||
$cities = $countryData['cities'];
|
||||
unset($countryData['cities']);
|
||||
$country = Country::firstOrCreate(['code' => $countryData['code']], $countryData);
|
||||
foreach ($cities as $cityName => $districts) {
|
||||
$city = City::firstOrCreate(['name' => $cityName, 'country_id' => $country->id]);
|
||||
foreach ($districts as $districtName) {
|
||||
District::firstOrCreate(['name' => $districtName, 'city_id' => $city->id]);
|
||||
}
|
||||
foreach ($countries as $country) {
|
||||
Country::firstOrCreate(['code' => $country['code']], array_merge($country, ['is_active' => true]));
|
||||
}
|
||||
|
||||
$tr = Country::where('code', 'TR')->first();
|
||||
if ($tr) {
|
||||
$cities = ['Istanbul', 'Ankara', 'Izmir', 'Bursa', 'Antalya'];
|
||||
foreach ($cities as $city) {
|
||||
City::firstOrCreate(['name' => $city, 'country_id' => $tr->id]);
|
||||
}
|
||||
}
|
||||
|
||||
$us = Country::where('code', 'US')->first();
|
||||
if ($us) {
|
||||
$cities = ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix'];
|
||||
foreach ($cities as $city) {
|
||||
City::firstOrCreate(['name' => $city, 'country_id' => $us->id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
65
Modules/Partner/Filament/Resources/ListingResource.php
Normal file
65
Modules/Partner/Filament/Resources/ListingResource.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
namespace Modules\Partner\Filament\Resources;
|
||||
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Modules\Category\Models\Category;
|
||||
use Modules\Listing\Models\Listing;
|
||||
use Modules\Partner\Filament\Resources\ListingResource\Pages;
|
||||
|
||||
class ListingResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Listing::class;
|
||||
protected static ?string $navigationIcon = 'heroicon-o-clipboard-document-list';
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form->schema([
|
||||
TextInput::make('title')->required()->maxLength(255)->live(onBlur: true)->afterStateUpdated(fn ($state, $set) => $set('slug', \Illuminate\Support\Str::slug($state) . '-' . \Illuminate\Support\Str::random(4))),
|
||||
TextInput::make('slug')->required()->maxLength(255)->unique(ignoreRecord: true),
|
||||
Textarea::make('description')->rows(4),
|
||||
TextInput::make('price')->numeric()->prefix('$'),
|
||||
TextInput::make('currency')->default('USD')->maxLength(3),
|
||||
Select::make('category_id')->label('Category')->options(fn () => Category::where('is_active', true)->pluck('name', 'id'))->searchable()->nullable(),
|
||||
TextInput::make('contact_phone')->tel()->maxLength(50),
|
||||
TextInput::make('contact_email')->email()->maxLength(255),
|
||||
TextInput::make('city')->maxLength(100),
|
||||
TextInput::make('country')->maxLength(100),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table->columns([
|
||||
TextColumn::make('title')->searchable()->sortable()->limit(40),
|
||||
TextColumn::make('category.name')->label('Category'),
|
||||
TextColumn::make('price')->money('USD')->sortable(),
|
||||
TextColumn::make('status')->badge()->color(fn ($state) => match ($state) { 'active' => 'success', 'sold' => 'gray', 'pending' => 'warning', default => 'danger' }),
|
||||
TextColumn::make('city'),
|
||||
TextColumn::make('created_at')->dateTime()->sortable(),
|
||||
])->actions([EditAction::make(), DeleteAction::make()]);
|
||||
}
|
||||
|
||||
public static function getEloquentQuery(): Builder
|
||||
{
|
||||
return parent::getEloquentQuery()->where('user_id', Filament::auth()->id());
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListListings::route('/'),
|
||||
'create' => Pages\CreateListing::route('/create'),
|
||||
'edit' => Pages\EditListing::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace Modules\Partner\Filament\Resources\ListingResource\Pages;
|
||||
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Modules\Partner\Filament\Resources\ListingResource;
|
||||
|
||||
class CreateListing extends CreateRecord
|
||||
{
|
||||
protected static string $resource = ListingResource::class;
|
||||
|
||||
protected function mutateFormDataBeforeCreate(array $data): array
|
||||
{
|
||||
$data['user_id'] = \Filament\Facades\Filament::auth()->id();
|
||||
$data['status'] = 'pending';
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace Modules\Partner\Filament\Resources\ListingResource\Pages;
|
||||
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Modules\Partner\Filament\Resources\ListingResource;
|
||||
|
||||
class EditListing extends EditRecord
|
||||
{
|
||||
protected static string $resource = ListingResource::class;
|
||||
protected function getHeaderActions(): array { return [DeleteAction::make()]; }
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace Modules\Partner\Filament\Resources\ListingResource\Pages;
|
||||
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Modules\Partner\Filament\Resources\ListingResource;
|
||||
|
||||
class ListListings extends ListRecords
|
||||
{
|
||||
protected static string $resource = ListingResource::class;
|
||||
protected function getHeaderActions(): array { return [CreateAction::make()]; }
|
||||
}
|
||||
47
Modules/Partner/Providers/PartnerPanelProvider.php
Normal file
47
Modules/Partner/Providers/PartnerPanelProvider.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace Modules\Partner\Providers;
|
||||
|
||||
use App\Models\User;
|
||||
use Filament\Http\Middleware\Authenticate;
|
||||
use Filament\Http\Middleware\AuthenticateSession;
|
||||
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||
use Filament\Pages\Dashboard;
|
||||
use Filament\Panel;
|
||||
use Filament\PanelProvider;
|
||||
use Filament\Support\Colors\Color;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
|
||||
class PartnerPanelProvider extends PanelProvider
|
||||
{
|
||||
public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
->id('partner')
|
||||
->path('partner')
|
||||
->login()
|
||||
->colors(['primary' => Color::Emerald])
|
||||
->tenant(User::class, slugAttribute: 'id')
|
||||
->discoverResources(in: module_path('Partner', 'Filament/Resources'), for: 'Modules\\Partner\\Filament\\Resources')
|
||||
->discoverPages(in: module_path('Partner', 'Filament/Pages'), for: 'Modules\\Partner\\Filament\\Pages')
|
||||
->discoverWidgets(in: module_path('Partner', 'Filament/Widgets'), for: 'Modules\\Partner\\Filament\\Widgets')
|
||||
->pages([Dashboard::class])
|
||||
->middleware([
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartSession::class,
|
||||
AuthenticateSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
DisableBladeIconComponents::class,
|
||||
DispatchServingFilamentEvent::class,
|
||||
])
|
||||
->authMiddleware([Authenticate::class]);
|
||||
}
|
||||
}
|
||||
14
Modules/Partner/Providers/PartnerServiceProvider.php
Normal file
14
Modules/Partner/Providers/PartnerServiceProvider.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Modules\Partner\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class PartnerServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function boot(): void {}
|
||||
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->register(PartnerPanelProvider::class);
|
||||
}
|
||||
}
|
||||
13
Modules/Partner/module.json
Normal file
13
Modules/Partner/module.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "Partner",
|
||||
"alias": "partner",
|
||||
"description": "Partner panel for users to manage their own listings",
|
||||
"keywords": [],
|
||||
"priority": 0,
|
||||
"providers": [
|
||||
"Modules\\Partner\\Providers\\PartnerServiceProvider"
|
||||
],
|
||||
"aliases": {},
|
||||
"files": [],
|
||||
"requires": []
|
||||
}
|
||||
265
README.md
265
README.md
@ -1,59 +1,244 @@
|
||||
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
|
||||
# OpenClassify
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
|
||||
</p>
|
||||
A modern classified ads platform built with Laravel 12, FilamentPHP v5, and Laravel Modules — similar to Letgo and Sahibinden.
|
||||
|
||||
## About Laravel
|
||||
## Features
|
||||
|
||||
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
|
||||
- 🛍️ **Classified Listings** — Browse, search, and post ads across categories
|
||||
- 🗂️ **Categories** — Hierarchical categories with icons
|
||||
- 📍 **Locations** — Country and city management
|
||||
- 👤 **User Profiles** — Manage your listings and account
|
||||
- 🔐 **Admin Panel** — Full control via FilamentPHP v5 at `/admin`
|
||||
- 🤝 **Partner Panel** — Users manage their own listings at `/partner/{id}` (tenant isolation)
|
||||
- 🌍 **10 Languages** — English, Turkish, Arabic, German, French, Spanish, Portuguese, Russian, Chinese, Japanese
|
||||
- 🐳 **Docker Ready** — One-command production and development setup
|
||||
- ☁️ **GitHub Codespaces** — Zero-config cloud development
|
||||
|
||||
- [Simple, fast routing engine](https://laravel.com/docs/routing).
|
||||
- [Powerful dependency injection container](https://laravel.com/docs/container).
|
||||
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
|
||||
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
|
||||
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
|
||||
- [Robust background job processing](https://laravel.com/docs/queues).
|
||||
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
|
||||
## Tech Stack
|
||||
|
||||
Laravel is accessible, powerful, and provides tools required for large, robust applications.
|
||||
| Layer | Technology |
|
||||
|-------|-----------|
|
||||
| Framework | Laravel 12 |
|
||||
| Admin UI | FilamentPHP v5 |
|
||||
| Modules | nWidart/laravel-modules v11 |
|
||||
| Auth/Roles | Spatie Laravel Permission |
|
||||
| Frontend | Blade + TailwindCSS + Vite |
|
||||
| Database | MySQL / SQLite |
|
||||
| Cache/Queue | Redis |
|
||||
|
||||
## Learning Laravel
|
||||
## Quick Start (Docker)
|
||||
|
||||
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. You can also check out [Laravel Learn](https://laravel.com/learn), where you will be guided through building a modern Laravel application.
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/openclassify/openclassify.git
|
||||
cd openclassify
|
||||
|
||||
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
|
||||
# Copy environment file
|
||||
cp .env.example .env
|
||||
|
||||
## Laravel Sponsors
|
||||
# Start with Docker Compose (production-like)
|
||||
docker compose up -d
|
||||
|
||||
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
|
||||
# The application will be available at http://localhost:8000
|
||||
```
|
||||
|
||||
### Premium Partners
|
||||
### Default Credentials
|
||||
|
||||
- **[Vehikl](https://vehikl.com)**
|
||||
- **[Tighten Co.](https://tighten.co)**
|
||||
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
|
||||
- **[64 Robots](https://64robots.com)**
|
||||
- **[Curotec](https://www.curotec.com/services/technologies/laravel)**
|
||||
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
|
||||
- **[Redberry](https://redberry.international/laravel-development)**
|
||||
- **[Active Logic](https://activelogic.com)**
|
||||
| Role | Email | Password |
|
||||
|------|-------|----------|
|
||||
| Admin | admin@openclassify.com | password |
|
||||
| Partner | partner@openclassify.com | password |
|
||||
|
||||
**Admin Panel:** http://localhost:8000/admin
|
||||
**Partner Panel:** http://localhost:8000/partner
|
||||
|
||||
---
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Option 1: GitHub Codespaces (Zero Config)
|
||||
|
||||
1. Click **Code → Codespaces → New codespace** on GitHub
|
||||
2. Wait for the environment to build (~2 minutes)
|
||||
3. The app starts automatically at port 8000
|
||||
|
||||
### Option 2: Docker Development
|
||||
|
||||
```bash
|
||||
# Start development environment with hot reload
|
||||
docker compose -f docker-compose.dev.yml up -d
|
||||
|
||||
# View logs
|
||||
docker compose -f docker-compose.dev.yml logs -f app
|
||||
```
|
||||
|
||||
### Option 3: Local (PHP + Node)
|
||||
|
||||
**Requirements:** PHP 8.2+, Composer, Node 18+, SQLite or MySQL
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
composer install
|
||||
npm install
|
||||
|
||||
# Setup environment
|
||||
cp .env.example .env
|
||||
php artisan key:generate
|
||||
|
||||
# Database (SQLite for quick start)
|
||||
touch database/database.sqlite
|
||||
php artisan migrate
|
||||
php artisan db:seed
|
||||
|
||||
# Start all services (server + queue + vite)
|
||||
composer run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### Module Structure
|
||||
|
||||
```
|
||||
Modules/
|
||||
├── Admin/ # FilamentPHP Admin Panel
|
||||
│ ├── Filament/
|
||||
│ │ └── Resources/ # CRUD resources (User, Category, Listing, Location)
|
||||
│ └── Providers/
|
||||
│ ├── AdminServiceProvider.php
|
||||
│ └── AdminPanelProvider.php
|
||||
│
|
||||
├── Partner/ # FilamentPHP Tenant Panel
|
||||
│ ├── Filament/
|
||||
│ │ └── Resources/ # Tenant-scoped Listing resource
|
||||
│ └── Providers/
|
||||
│ ├── PartnerServiceProvider.php
|
||||
│ └── PartnerPanelProvider.php
|
||||
│
|
||||
├── Category/ # Category management
|
||||
│ ├── Models/Category.php
|
||||
│ ├── Http/Controllers/
|
||||
│ ├── database/migrations/
|
||||
│ └── database/seeders/
|
||||
│
|
||||
├── Listing/ # Listing management
|
||||
│ ├── Models/Listing.php
|
||||
│ ├── Http/Controllers/
|
||||
│ ├── database/migrations/
|
||||
│ └── database/seeders/
|
||||
│
|
||||
├── Location/ # Countries & Cities
|
||||
│ ├── Models/{Country,City,District}.php
|
||||
│ ├── database/migrations/
|
||||
│ └── database/seeders/
|
||||
│
|
||||
└── Profile/ # User profile pages
|
||||
├── Models/Profile.php
|
||||
├── Http/Controllers/
|
||||
└── database/migrations/
|
||||
```
|
||||
|
||||
### Panels
|
||||
|
||||
| Panel | URL | Access |
|
||||
|-------|-----|--------|
|
||||
| Admin | `/admin` | Users with `admin` role |
|
||||
| Partner | `/partner/{id}` | All authenticated users (tenant-scoped) |
|
||||
|
||||
### Roles (Spatie Permission)
|
||||
|
||||
| Role | Access |
|
||||
|------|--------|
|
||||
| `admin` | Full admin panel access |
|
||||
| `partner` | Partner panel only (manages own listings) |
|
||||
|
||||
---
|
||||
|
||||
## Creating a New Module
|
||||
|
||||
```bash
|
||||
php artisan module:make ModuleName
|
||||
```
|
||||
|
||||
Then add to `modules_statuses.json`:
|
||||
```json
|
||||
{
|
||||
"ModuleName": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Adding a Filament Resource to Admin Panel
|
||||
|
||||
Resources are auto-discovered from `Modules/Admin/Filament/Resources/`.
|
||||
|
||||
---
|
||||
|
||||
## Language Support
|
||||
|
||||
Languages are in `lang/{locale}/messages.php`. To add a new language:
|
||||
|
||||
1. Create `lang/{locale}/messages.php`
|
||||
2. Switch language via: `GET /lang/{locale}`
|
||||
|
||||
Supported locales: `en`, `tr`, `ar`, `de`, `fr`, `es`, `pt`, `ru`, `zh`, `ja`
|
||||
|
||||
---
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
php artisan test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Production Deployment
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```env
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
APP_URL=https://yourdomain.com
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=your-db-host
|
||||
DB_DATABASE=openclassify
|
||||
DB_USERNAME=openclassify
|
||||
DB_PASSWORD=your-secure-password
|
||||
|
||||
REDIS_HOST=your-redis-host
|
||||
CACHE_STORE=redis
|
||||
SESSION_DRIVER=redis
|
||||
QUEUE_CONNECTION=redis
|
||||
```
|
||||
|
||||
### Post-Deploy Commands
|
||||
|
||||
```bash
|
||||
php artisan migrate --force
|
||||
php artisan db:seed --force # Only on first deploy
|
||||
php artisan config:cache
|
||||
php artisan route:cache
|
||||
php artisan view:cache
|
||||
php artisan storage:link
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
|
||||
1. Fork the repository
|
||||
2. Create a feature branch: `git checkout -b feature/your-feature`
|
||||
3. Commit your changes: `git commit -m 'Add your feature'`
|
||||
4. Push to the branch: `git push origin feature/your-feature`
|
||||
5. Open a Pull Request
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
|
||||
|
||||
## Security Vulnerabilities
|
||||
|
||||
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
||||
MIT License. See [LICENSE](LICENSE) for details.
|
||||
|
||||
@ -1,43 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Filament\Models\Contracts\FilamentUser;
|
||||
use Filament\Models\Contracts\HasTenants;
|
||||
use Filament\Panel;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
|
||||
class User extends Authenticatable
|
||||
class User extends Authenticatable implements FilamentUser, HasTenants
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
use HasFactory, HasRoles, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
];
|
||||
protected $fillable = ['name', 'email', 'password'];
|
||||
protected $hidden = ['password', 'remember_token'];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
@ -45,4 +25,28 @@ class User extends Authenticatable
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
|
||||
public function canAccessPanel(Panel $panel): bool
|
||||
{
|
||||
return match ($panel->getId()) {
|
||||
'admin' => $this->hasRole('admin'),
|
||||
'partner' => true,
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
public function getTenants(Panel $panel): Collection
|
||||
{
|
||||
return collect([$this]);
|
||||
}
|
||||
|
||||
public function canAccessTenant(Model $tenant): bool
|
||||
{
|
||||
return $tenant->getKey() === $this->getKey();
|
||||
}
|
||||
|
||||
public function listings()
|
||||
{
|
||||
return $this->hasMany(\Modules\Listing\Models\Listing::class);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,13 +7,14 @@
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"filament/filament": "^5.0",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/tinker": "^2.10.1",
|
||||
"nwidart/laravel-modules": "^11.0"
|
||||
"nwidart/laravel-modules": "^11.0",
|
||||
"spatie/laravel-permission": "^7.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
"laravel/breeze": "*",
|
||||
"laravel/pail": "^1.2.2",
|
||||
"laravel/pint": "^1.24",
|
||||
"laravel/sail": "^1.41",
|
||||
@ -53,7 +54,8 @@
|
||||
],
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover --ansi"
|
||||
"@php artisan package:discover --ansi",
|
||||
"@php artisan filament:upgrade"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
|
||||
|
||||
2337
composer.lock
generated
2337
composer.lock
generated
File diff suppressed because it is too large
Load Diff
202
config/permission.php
Normal file
202
config/permission.php
Normal file
@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'models' => [
|
||||
|
||||
/*
|
||||
* When using the "HasPermissions" trait from this package, we need to know which
|
||||
* Eloquent model should be used to retrieve your permissions. Of course, it
|
||||
* is often just the "Permission" model but you may use whatever you like.
|
||||
*
|
||||
* The model you want to use as a Permission model needs to implement the
|
||||
* `Spatie\Permission\Contracts\Permission` contract.
|
||||
*/
|
||||
|
||||
'permission' => Spatie\Permission\Models\Permission::class,
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* Eloquent model should be used to retrieve your roles. Of course, it
|
||||
* is often just the "Role" model but you may use whatever you like.
|
||||
*
|
||||
* The model you want to use as a Role model needs to implement the
|
||||
* `Spatie\Permission\Contracts\Role` contract.
|
||||
*/
|
||||
|
||||
'role' => Spatie\Permission\Models\Role::class,
|
||||
|
||||
],
|
||||
|
||||
'table_names' => [
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* table should be used to retrieve your roles. We have chosen a basic
|
||||
* default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'roles' => 'roles',
|
||||
|
||||
/*
|
||||
* When using the "HasPermissions" trait from this package, we need to know which
|
||||
* table should be used to retrieve your permissions. We have chosen a basic
|
||||
* default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'permissions' => 'permissions',
|
||||
|
||||
/*
|
||||
* When using the "HasPermissions" trait from this package, we need to know which
|
||||
* table should be used to retrieve your models permissions. We have chosen a
|
||||
* basic default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'model_has_permissions' => 'model_has_permissions',
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* table should be used to retrieve your models roles. We have chosen a
|
||||
* basic default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'model_has_roles' => 'model_has_roles',
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* table should be used to retrieve your roles permissions. We have chosen a
|
||||
* basic default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'role_has_permissions' => 'role_has_permissions',
|
||||
],
|
||||
|
||||
'column_names' => [
|
||||
/*
|
||||
* Change this if you want to name the related pivots other than defaults
|
||||
*/
|
||||
'role_pivot_key' => null, // default 'role_id',
|
||||
'permission_pivot_key' => null, // default 'permission_id',
|
||||
|
||||
/*
|
||||
* Change this if you want to name the related model primary key other than
|
||||
* `model_id`.
|
||||
*
|
||||
* For example, this would be nice if your primary keys are all UUIDs. In
|
||||
* that case, name this `model_uuid`.
|
||||
*/
|
||||
|
||||
'model_morph_key' => 'model_id',
|
||||
|
||||
/*
|
||||
* Change this if you want to use the teams feature and your related model's
|
||||
* foreign key is other than `team_id`.
|
||||
*/
|
||||
|
||||
'team_foreign_key' => 'team_id',
|
||||
],
|
||||
|
||||
/*
|
||||
* When set to true, the method for checking permissions will be registered on the gate.
|
||||
* Set this to false if you want to implement custom logic for checking permissions.
|
||||
*/
|
||||
|
||||
'register_permission_check_method' => true,
|
||||
|
||||
/*
|
||||
* When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered
|
||||
* this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated
|
||||
* NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it.
|
||||
*/
|
||||
'register_octane_reset_listener' => false,
|
||||
|
||||
/*
|
||||
* Events will fire when a role or permission is assigned/unassigned:
|
||||
* \Spatie\Permission\Events\RoleAttached
|
||||
* \Spatie\Permission\Events\RoleDetached
|
||||
* \Spatie\Permission\Events\PermissionAttached
|
||||
* \Spatie\Permission\Events\PermissionDetached
|
||||
*
|
||||
* To enable, set to true, and then create listeners to watch these events.
|
||||
*/
|
||||
'events_enabled' => false,
|
||||
|
||||
/*
|
||||
* Teams Feature.
|
||||
* When set to true the package implements teams using the 'team_foreign_key'.
|
||||
* If you want the migrations to register the 'team_foreign_key', you must
|
||||
* set this to true before doing the migration.
|
||||
* If you already did the migration then you must make a new migration to also
|
||||
* add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions'
|
||||
* (view the latest version of this package's migration file)
|
||||
*/
|
||||
|
||||
'teams' => false,
|
||||
|
||||
/*
|
||||
* The class to use to resolve the permissions team id
|
||||
*/
|
||||
'team_resolver' => \Spatie\Permission\DefaultTeamResolver::class,
|
||||
|
||||
/*
|
||||
* Passport Client Credentials Grant
|
||||
* When set to true the package will use Passports Client to check permissions
|
||||
*/
|
||||
|
||||
'use_passport_client_credentials' => false,
|
||||
|
||||
/*
|
||||
* When set to true, the required permission names are added to exception messages.
|
||||
* This could be considered an information leak in some contexts, so the default
|
||||
* setting is false here for optimum safety.
|
||||
*/
|
||||
|
||||
'display_permission_in_exception' => false,
|
||||
|
||||
/*
|
||||
* When set to true, the required role names are added to exception messages.
|
||||
* This could be considered an information leak in some contexts, so the default
|
||||
* setting is false here for optimum safety.
|
||||
*/
|
||||
|
||||
'display_role_in_exception' => false,
|
||||
|
||||
/*
|
||||
* By default wildcard permission lookups are disabled.
|
||||
* See documentation to understand supported syntax.
|
||||
*/
|
||||
|
||||
'enable_wildcard_permission' => false,
|
||||
|
||||
/*
|
||||
* The class to use for interpreting wildcard permissions.
|
||||
* If you need to modify delimiters, override the class and specify its name here.
|
||||
*/
|
||||
// 'wildcard_permission' => Spatie\Permission\WildcardPermission::class,
|
||||
|
||||
/* Cache-specific settings */
|
||||
|
||||
'cache' => [
|
||||
|
||||
/*
|
||||
* By default all permissions are cached for 24 hours to speed up performance.
|
||||
* When permissions or roles are updated the cache is flushed automatically.
|
||||
*/
|
||||
|
||||
'expiration_time' => \DateInterval::createFromDateString('24 hours'),
|
||||
|
||||
/*
|
||||
* The cache key used to store all permissions.
|
||||
*/
|
||||
|
||||
'key' => 'spatie.permission.cache',
|
||||
|
||||
/*
|
||||
* You may optionally indicate a specific cache driver to use for permission and
|
||||
* role caching using any of the `store` drivers listed in the cache.php config
|
||||
* file. Using 'default' here means to use the `default` set in cache.php.
|
||||
*/
|
||||
|
||||
'store' => 'default',
|
||||
],
|
||||
];
|
||||
@ -0,0 +1,137 @@
|
||||
<?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
|
||||
{
|
||||
$teams = config('permission.teams');
|
||||
$tableNames = config('permission.table_names');
|
||||
$columnNames = config('permission.column_names');
|
||||
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
|
||||
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
|
||||
|
||||
throw_if(empty($tableNames), 'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), 'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
|
||||
/**
|
||||
* See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered.
|
||||
*/
|
||||
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
|
||||
$table->id(); // permission id
|
||||
$table->string('name');
|
||||
$table->string('guard_name');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['name', 'guard_name']);
|
||||
});
|
||||
|
||||
/**
|
||||
* See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered.
|
||||
*/
|
||||
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
|
||||
$table->id(); // role id
|
||||
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
|
||||
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
|
||||
}
|
||||
$table->string('name');
|
||||
$table->string('guard_name');
|
||||
$table->timestamps();
|
||||
if ($teams || config('permission.testing')) {
|
||||
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
|
||||
} else {
|
||||
$table->unique(['name', 'guard_name']);
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->on($tableNames['permissions'])
|
||||
->cascadeOnDelete();
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id') // role id
|
||||
->on($tableNames['roles'])
|
||||
->cascadeOnDelete();
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->on($tableNames['permissions'])
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id') // role id
|
||||
->on($tableNames['roles'])
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
|
||||
});
|
||||
|
||||
app('cache')
|
||||
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
||||
->forget(config('permission.cache.key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$tableNames = config('permission.table_names');
|
||||
|
||||
throw_if(empty($tableNames), 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
|
||||
|
||||
Schema::dropIfExists($tableNames['role_has_permissions']);
|
||||
Schema::dropIfExists($tableNames['model_has_roles']);
|
||||
Schema::dropIfExists($tableNames['model_has_permissions']);
|
||||
Schema::dropIfExists($tableNames['roles']);
|
||||
Schema::dropIfExists($tableNames['permissions']);
|
||||
}
|
||||
};
|
||||
@ -1,25 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class DatabaseSeeder extends Seeder
|
||||
{
|
||||
use WithoutModelEvents;
|
||||
|
||||
/**
|
||||
* Seed the application's database.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// User::factory(10)->create();
|
||||
$admin = \App\Models\User::factory()->create([
|
||||
'name' => 'Admin User',
|
||||
'email' => 'admin@openclassify.com',
|
||||
'password' => Hash::make('password'),
|
||||
]);
|
||||
|
||||
User::factory()->create([
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@example.com',
|
||||
$partner = \App\Models\User::factory()->create([
|
||||
'name' => 'Partner User',
|
||||
'email' => 'partner@openclassify.com',
|
||||
'password' => Hash::make('password'),
|
||||
]);
|
||||
|
||||
if (class_exists(\Spatie\Permission\Models\Role::class)) {
|
||||
$adminRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'admin', 'guard_name' => 'web']);
|
||||
\Spatie\Permission\Models\Role::firstOrCreate(['name' => 'partner', 'guard_name' => 'web']);
|
||||
$admin->assignRole($adminRole);
|
||||
}
|
||||
|
||||
$this->call([
|
||||
\Modules\Location\Database\Seeders\LocationSeeder::class,
|
||||
\Modules\Category\Database\Seeders\CategorySeeder::class,
|
||||
\Modules\Listing\Database\Seeders\ListingSeeder::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
64
docker-compose.dev.yml
Normal file
64
docker-compose.dev.yml
Normal file
@ -0,0 +1,64 @@
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
ports:
|
||||
- "8000:80"
|
||||
- "5173:5173"
|
||||
environment:
|
||||
APP_ENV: local
|
||||
APP_DEBUG: "true"
|
||||
DB_CONNECTION: mysql
|
||||
DB_HOST: db
|
||||
DB_PORT: 3306
|
||||
DB_DATABASE: openclassify
|
||||
DB_USERNAME: openclassify
|
||||
DB_PASSWORD: secret
|
||||
REDIS_HOST: redis
|
||||
CACHE_STORE: redis
|
||||
SESSION_DRIVER: redis
|
||||
QUEUE_CONNECTION: redis
|
||||
volumes:
|
||||
- .:/var/www/html
|
||||
- /var/www/html/vendor
|
||||
- /var/www/html/node_modules
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
|
||||
db:
|
||||
image: mysql:8.0
|
||||
environment:
|
||||
MYSQL_DATABASE: openclassify
|
||||
MYSQL_USER: openclassify
|
||||
MYSQL_PASSWORD: secret
|
||||
MYSQL_ROOT_PASSWORD: rootsecret
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- db_data_dev:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data_dev:/data
|
||||
|
||||
mailpit:
|
||||
image: axllent/mailpit:latest
|
||||
ports:
|
||||
- "8025:8025"
|
||||
- "1025:1025"
|
||||
|
||||
volumes:
|
||||
db_data_dev:
|
||||
redis_data_dev:
|
||||
52
docker-compose.yml
Normal file
52
docker-compose.yml
Normal file
@ -0,0 +1,52 @@
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "8000:80"
|
||||
environment:
|
||||
APP_ENV: production
|
||||
APP_DEBUG: "false"
|
||||
DB_CONNECTION: mysql
|
||||
DB_HOST: db
|
||||
DB_PORT: 3306
|
||||
DB_DATABASE: openclassify
|
||||
DB_USERNAME: openclassify
|
||||
DB_PASSWORD: secret
|
||||
REDIS_HOST: redis
|
||||
CACHE_STORE: redis
|
||||
SESSION_DRIVER: redis
|
||||
QUEUE_CONNECTION: redis
|
||||
volumes:
|
||||
- storage_data:/var/www/html/storage/app
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
|
||||
db:
|
||||
image: mysql:8.0
|
||||
environment:
|
||||
MYSQL_DATABASE: openclassify
|
||||
MYSQL_USER: openclassify
|
||||
MYSQL_PASSWORD: secret
|
||||
MYSQL_ROOT_PASSWORD: rootsecret
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
redis_data:
|
||||
storage_data:
|
||||
37
docker/nginx.conf
Normal file
37
docker/nginx.conf
Normal file
@ -0,0 +1,37 @@
|
||||
user www-data;
|
||||
worker_processes auto;
|
||||
pid /run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
client_max_body_size 20M;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /var/www/html/public;
|
||||
index index.php;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
|
||||
location ~ /\.(?!well-known).* {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
docker/start-dev.sh
Normal file
20
docker/start-dev.sh
Normal file
@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [ ! -f /var/www/html/.env ]; then
|
||||
cp /var/www/html/.env.example /var/www/html/.env
|
||||
fi
|
||||
|
||||
cd /var/www/html
|
||||
|
||||
composer install
|
||||
npm install
|
||||
|
||||
php artisan key:generate --force
|
||||
php artisan migrate --force
|
||||
php artisan db:seed --force
|
||||
php artisan storage:link
|
||||
|
||||
php-fpm -D
|
||||
npm run dev &
|
||||
nginx -g 'daemon off;'
|
||||
17
docker/start.sh
Normal file
17
docker/start.sh
Normal file
@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [ ! -f /var/www/html/.env ]; then
|
||||
cp /var/www/html/.env.example /var/www/html/.env
|
||||
php artisan key:generate --force
|
||||
fi
|
||||
|
||||
php artisan migrate --force
|
||||
php artisan db:seed --force
|
||||
php artisan config:cache
|
||||
php artisan route:cache
|
||||
php artisan view:cache
|
||||
php artisan storage:link
|
||||
|
||||
php-fpm -D
|
||||
nginx -g 'daemon off;'
|
||||
@ -2,5 +2,7 @@
|
||||
"Category": true,
|
||||
"Listing": true,
|
||||
"Location": true,
|
||||
"Profile": true
|
||||
"Profile": true,
|
||||
"Admin": true,
|
||||
"Partner": true
|
||||
}
|
||||
|
||||
2
public/css/filament/filament/app.css
Normal file
2
public/css/filament/filament/app.css
Normal file
File diff suppressed because one or more lines are too long
1
public/fonts/filament/filament/inter/index.css
Normal file
1
public/fonts/filament/filament/inter/index.css
Normal file
@ -0,0 +1 @@
|
||||
@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-ext-wght-normal-IYF56FF6.woff2") format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-wght-normal-JEOLYBOO.woff2") format("woff2-variations");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-ext-wght-normal-EOVOK2B5.woff2") format("woff2-variations");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-wght-normal-IRE366VL.woff2") format("woff2-variations");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-vietnamese-wght-normal-CE5GGD3W.woff2") format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-ext-wght-normal-HA22NDSG.woff2") format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-wght-normal-NRMW37G5.woff2") format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1
public/js/filament/actions/actions.js
Normal file
1
public/js/filament/actions/actions.js
Normal file
@ -0,0 +1 @@
|
||||
(()=>{var n=({livewireId:e})=>({actionNestingIndex:null,init(){window.addEventListener("sync-action-modals",t=>{t.detail.id===e&&this.syncActionModals(t.detail.newActionNestingIndex,t.detail.shouldOverlayParentActions??!1)})},syncActionModals(t,i=!1){if(this.actionNestingIndex===t){this.actionNestingIndex!==null&&this.$nextTick(()=>this.openModal());return}let s=this.actionNestingIndex!==null&&t!==null&&t>this.actionNestingIndex;if(this.actionNestingIndex!==null&&!(i&&s)&&this.closeModal(),this.actionNestingIndex=t,this.actionNestingIndex!==null){if(!this.$el.querySelector(`#${this.generateModalId(t)}`)){this.$nextTick(()=>this.openModal());return}this.openModal()}},generateModalId(t){return`fi-${e}-action-`+t},openModal(){let t=this.generateModalId(this.actionNestingIndex);document.dispatchEvent(new CustomEvent("open-modal",{bubbles:!0,composed:!0,detail:{id:t}}))},closeModal(){let t=this.generateModalId(this.actionNestingIndex);document.dispatchEvent(new CustomEvent("close-modal-quietly",{bubbles:!0,composed:!0,detail:{id:t}}))}});document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentActionModals",n)});})();
|
||||
1
public/js/filament/filament/app.js
Normal file
1
public/js/filament/filament/app.js
Normal file
File diff suppressed because one or more lines are too long
13
public/js/filament/filament/echo.js
Normal file
13
public/js/filament/filament/echo.js
Normal file
File diff suppressed because one or more lines are too long
1
public/js/filament/forms/components/checkbox-list.js
Normal file
1
public/js/filament/forms/components/checkbox-list.js
Normal file
@ -0,0 +1 @@
|
||||
function c({livewireId:s}){return{areAllCheckboxesChecked:!1,checkboxListOptions:[],search:"",unsubscribeLivewireHook:null,visibleCheckboxListOptions:[],init(){this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.$nextTick(()=>{this.checkIfAllCheckboxesAreChecked()}),this.unsubscribeLivewireHook=Livewire.interceptMessage(({message:e,onSuccess:t})=>{t(()=>{this.$nextTick(()=>{e.component.id===s&&(this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked())})})}),this.$watch("search",()=>{this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked()})},checkIfAllCheckboxesAreChecked(){this.areAllCheckboxesChecked=this.visibleCheckboxListOptions.length===this.visibleCheckboxListOptions.filter(e=>e.querySelector("input[type=checkbox]:checked, input[type=checkbox]:disabled")).length},toggleAllCheckboxes(){this.checkIfAllCheckboxesAreChecked();let e=!this.areAllCheckboxesChecked;this.visibleCheckboxListOptions.forEach(t=>{let i=t.querySelector("input[type=checkbox]");i.disabled||i.checked!==e&&(i.checked=e,i.dispatchEvent(new Event("change")))}),this.areAllCheckboxesChecked=e},updateVisibleCheckboxListOptions(){this.visibleCheckboxListOptions=this.checkboxListOptions.filter(e=>["",null,void 0].includes(this.search)||e.querySelector(".fi-fo-checkbox-list-option-label")?.innerText.toLowerCase().includes(this.search.toLowerCase())?!0:e.querySelector(".fi-fo-checkbox-list-option-description")?.innerText.toLowerCase().includes(this.search.toLowerCase()))},destroy(){this.unsubscribeLivewireHook?.()}}}export{c as default};
|
||||
38
public/js/filament/forms/components/code-editor.js
Normal file
38
public/js/filament/forms/components/code-editor.js
Normal file
File diff suppressed because one or more lines are too long
1
public/js/filament/forms/components/color-picker.js
Normal file
1
public/js/filament/forms/components/color-picker.js
Normal file
File diff suppressed because one or more lines are too long
1
public/js/filament/forms/components/date-time-picker.js
Normal file
1
public/js/filament/forms/components/date-time-picker.js
Normal file
File diff suppressed because one or more lines are too long
123
public/js/filament/forms/components/file-upload.js
Normal file
123
public/js/filament/forms/components/file-upload.js
Normal file
File diff suppressed because one or more lines are too long
1
public/js/filament/forms/components/key-value.js
Normal file
1
public/js/filament/forms/components/key-value.js
Normal file
@ -0,0 +1 @@
|
||||
function a({state:r}){return{state:r,rows:[],init(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(e,t)=>{if(!Array.isArray(e))return;let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(e)===0&&s(t)===0||this.updateRows()})},addRow(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow(e){this.rows.splice(e,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows(e){let t=Alpine.raw(this.rows);this.rows=[];let s=t.splice(e.oldIndex,1)[0];t.splice(e.newIndex,0,s),this.$nextTick(()=>{this.rows=t,this.updateState()})},updateRows(){let t=Alpine.raw(this.state).map(({key:s,value:i})=>({key:s,value:i}));this.rows.forEach(s=>{(s.key===""||s.key===null)&&t.push({key:"",value:s.value})}),this.rows=t},updateState(){let e=[];this.rows.forEach(t=>{t.key===""||t.key===null||e.push({key:t.key,value:t.value})}),JSON.stringify(this.state)!==JSON.stringify(e)&&(this.state=e)}}}export{a as default};
|
||||
51
public/js/filament/forms/components/markdown-editor.js
Normal file
51
public/js/filament/forms/components/markdown-editor.js
Normal file
File diff suppressed because one or more lines are too long
144
public/js/filament/forms/components/rich-editor.js
Normal file
144
public/js/filament/forms/components/rich-editor.js
Normal file
File diff suppressed because one or more lines are too long
11
public/js/filament/forms/components/select.js
Normal file
11
public/js/filament/forms/components/select.js
Normal file
File diff suppressed because one or more lines are too long
1
public/js/filament/forms/components/slider.js
Normal file
1
public/js/filament/forms/components/slider.js
Normal file
File diff suppressed because one or more lines are too long
1
public/js/filament/forms/components/tags-input.js
Normal file
1
public/js/filament/forms/components/tags-input.js
Normal file
@ -0,0 +1 @@
|
||||
function s({state:n,splitKeys:a}){return{newTag:"",state:n,createTag(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag(t){this.state=this.state.filter(e=>e!==t)},reorderTags(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{"x-on:blur":"createTag()","x-model":"newTag","x-on:keydown"(t){["Enter",...a].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},"x-on:paste"(){this.$nextTick(()=>{if(a.length===0){this.createTag();return}let t=a.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{s as default};
|
||||
1
public/js/filament/forms/components/textarea.js
Normal file
1
public/js/filament/forms/components/textarea.js
Normal file
@ -0,0 +1 @@
|
||||
function n({initialHeight:e,shouldAutosize:i,state:h}){return{state:h,wrapperEl:null,init(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=e+"rem")},resize(){if(this.$el.scrollHeight<=0)return;let t=this.$el.style.height;this.$el.style.height="0px";let r=this.$el.scrollHeight;this.$el.style.height=t;let l=parseFloat(e)*parseFloat(getComputedStyle(document.documentElement).fontSize),s=Math.max(r,l)+"px";this.wrapperEl.style.height!==s&&(this.wrapperEl.style.height=s)},setUpResizeObserver(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{n as default};
|
||||
1
public/js/filament/notifications/notifications.js
Normal file
1
public/js/filament/notifications/notifications.js
Normal file
@ -0,0 +1 @@
|
||||
(()=>{function c(s,t=()=>{}){let i=!1;return function(){i?t.apply(this,arguments):(i=!0,s.apply(this,arguments))}}var d=s=>{s.data("notificationComponent",({notification:t})=>({isShown:!1,computedStyle:null,transitionDuration:null,transitionEasing:null,unsubscribeLivewireHook:null,init(){this.computedStyle=window.getComputedStyle(this.$el),this.transitionDuration=parseFloat(this.computedStyle.transitionDuration)*1e3,this.transitionEasing=this.computedStyle.transitionTimingFunction,this.configureTransitions(),this.configureAnimations(),t.duration&&t.duration!=="persistent"&&setTimeout(()=>{if(!this.$el.matches(":hover")){this.close();return}this.$el.addEventListener("mouseleave",()=>this.close())},t.duration),this.isShown=!0},configureTransitions(){let i=this.computedStyle.display,e=()=>{s.mutateDom(()=>{this.$el.style.setProperty("display",i),this.$el.style.setProperty("visibility","visible")}),this.$el._x_isShown=!0},o=()=>{s.mutateDom(()=>{this.$el._x_isShown?this.$el.style.setProperty("visibility","hidden"):this.$el.style.setProperty("display","none")})},r=c(n=>n?e():o(),n=>{this.$el._x_toggleAndCascadeWithTransitions(this.$el,n,e,o)});s.effect(()=>r(this.isShown))},configureAnimations(){let i;this.unsubscribeLivewireHook=Livewire.interceptMessage(({onFinish:e,onSuccess:o})=>{requestAnimationFrame(()=>{let r=()=>this.$el.getBoundingClientRect().top,n=r();e(()=>{i=()=>{this.isShown&&this.$el.animate([{transform:`translateY(${n-r()}px)`},{transform:"translateY(0px)"}],{duration:this.transitionDuration,easing:this.transitionEasing})},this.$el.getAnimations().forEach(l=>l.finish())}),o(({payload:l})=>{l?.snapshot?.data?.isFilamentNotificationsComponent&&typeof i=="function"&&i()})})})},close(){this.isShown=!1,setTimeout(()=>window.dispatchEvent(new CustomEvent("notificationClosed",{detail:{id:t.id}})),this.transitionDuration)},markAsRead(){window.dispatchEvent(new CustomEvent("markedNotificationAsRead",{detail:{id:t.id}}))},markAsUnread(){window.dispatchEvent(new CustomEvent("markedNotificationAsUnread",{detail:{id:t.id}}))},destroy(){this.unsubscribeLivewireHook?.()}}))};var h=class{constructor(){return this.id(crypto.randomUUID()),this}id(t){return this.id=t,this}title(t){return this.title=t,this}body(t){return this.body=t,this}actions(t){return this.actions=t,this}status(t){return this.status=t,this}color(t){return this.color=t,this}icon(t){return this.icon=t,this}iconColor(t){return this.iconColor=t,this}duration(t){return this.duration=t,this}seconds(t){return this.duration(t*1e3),this}persistent(){return this.duration("persistent"),this}danger(){return this.status("danger"),this}info(){return this.status("info"),this}success(){return this.status("success"),this}warning(){return this.status("warning"),this}view(t){return this.view=t,this}viewData(t){return this.viewData=t,this}send(){return window.dispatchEvent(new CustomEvent("notificationSent",{detail:{notification:this}})),this}},a=class{constructor(t){return this.name(t),this}name(t){return this.name=t,this}color(t){return this.color=t,this}dispatch(t,i){return this.event(t),this.eventData(i),this}dispatchSelf(t,i){return this.dispatch(t,i),this.dispatchDirection="self",this}dispatchTo(t,i,e){return this.dispatch(i,e),this.dispatchDirection="to",this.dispatchToComponent=t,this}emit(t,i){return this.dispatch(t,i),this}emitSelf(t,i){return this.dispatchSelf(t,i),this}emitTo(t,i,e){return this.dispatchTo(t,i,e),this}dispatchDirection(t){return this.dispatchDirection=t,this}dispatchToComponent(t){return this.dispatchToComponent=t,this}event(t){return this.event=t,this}eventData(t){return this.eventData=t,this}extraAttributes(t){return this.extraAttributes=t,this}icon(t){return this.icon=t,this}iconPosition(t){return this.iconPosition=t,this}outlined(t=!0){return this.isOutlined=t,this}disabled(t=!0){return this.isDisabled=t,this}label(t){return this.label=t,this}close(t=!0){return this.shouldClose=t,this}openUrlInNewTab(t=!0){return this.shouldOpenUrlInNewTab=t,this}size(t){return this.size=t,this}url(t){return this.url=t,this}view(t){return this.view=t,this}button(){return this.view("filament::components.button.index"),this}grouped(){return this.view("filament::components.dropdown.list.item"),this}iconButton(){return this.view("filament::components.icon-button"),this}link(){return this.view("filament::components.link"),this}},u=class{constructor(t){return this.actions(t),this}actions(t){return this.actions=t.map(i=>i.grouped()),this}color(t){return this.color=t,this}icon(t){return this.icon=t,this}iconPosition(t){return this.iconPosition=t,this}label(t){return this.label=t,this}tooltip(t){return this.tooltip=t,this}};window.FilamentNotificationAction=a;window.FilamentNotificationActionGroup=u;window.FilamentNotification=h;document.addEventListener("alpine:init",()=>{window.Alpine.plugin(d)});})();
|
||||
1
public/js/filament/schemas/components/actions.js
Normal file
1
public/js/filament/schemas/components/actions.js
Normal file
@ -0,0 +1 @@
|
||||
var i=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let e=this.$el.parentElement;e&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(e),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let e=this.$el.parentElement;if(!e)return;let t=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=e.offsetWidth+parseInt(t.marginInlineStart,10)*-1+parseInt(t.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});export{i as default};
|
||||
1
public/js/filament/schemas/components/tabs.js
Normal file
1
public/js/filament/schemas/components/tabs.js
Normal file
@ -0,0 +1 @@
|
||||
function v({activeTab:w,isScrollable:f,isTabPersistedInQueryString:m,livewireId:g,tab:T,tabQueryStringKey:r}){return{boundResizeHandler:null,isScrollable:f,resizeDebounceTimer:null,tab:T,unsubscribeLivewireHook:null,withinDropdownIndex:null,withinDropdownMounted:!1,init(){let t=this.getTabs(),e=new URLSearchParams(window.location.search);m&&e.has(r)&&t.includes(e.get(r))&&(this.tab=e.get(r)),(!this.tab||!t.includes(this.tab))&&(this.tab=t[w-1]),this.$watch("tab",()=>{this.updateQueryString(),this.autofocusFields()}),this.autofocusFields(!0),this.unsubscribeLivewireHook=Livewire.interceptMessage(({message:i,onSuccess:a})=>{a(()=>{this.$nextTick(()=>{if(i.component.id!==g)return;let l=this.getTabs();l.includes(this.tab)||(this.tab=l[w-1]??this.tab)})})}),f||(this.boundResizeHandler=this.debouncedUpdateTabsWithinDropdown.bind(this),window.addEventListener("resize",this.boundResizeHandler),this.updateTabsWithinDropdown())},calculateAvailableWidth(t){let e=window.getComputedStyle(t);return Math.floor(t.clientWidth)-Math.ceil(parseFloat(e.paddingLeft))*2},calculateContainerGap(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.columnGap))},calculateDropdownIconWidth(t){let e=t.querySelector(".fi-icon");return Math.ceil(e.clientWidth)},calculateTabItemGap(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.columnGap)||8)},calculateTabItemPadding(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.paddingLeft))+Math.ceil(parseFloat(e.paddingRight))},findOverflowIndex(t,e,i,a,l,h){let u=t.map(n=>Math.ceil(n.clientWidth)),b=t.map(n=>{let c=n.querySelector(".fi-tabs-item-label"),s=n.querySelector(".fi-badge"),o=Math.ceil(c.clientWidth),d=s?Math.ceil(s.clientWidth):0;return{label:o,badge:d,total:o+(d>0?a+d:0)}});for(let n=0;n<t.length;n++){let c=u.slice(0,n+1).reduce((p,y)=>p+y,0),s=n*i,o=b.slice(n+1),d=o.length>0,D=d?Math.max(...o.map(p=>p.total)):0,W=d?l+D+a+h+i:0;if(c+s+W>e)return n}return-1},get isDropdownButtonVisible(){return this.withinDropdownMounted?this.withinDropdownIndex===null?!1:this.getTabs().findIndex(e=>e===this.tab)<this.withinDropdownIndex:!0},getTabs(){return this.$refs.tabsData?JSON.parse(this.$refs.tabsData.value):[]},updateQueryString(){if(!m)return;let t=new URL(window.location.href);t.searchParams.set(r,this.tab),history.replaceState(null,document.title,t.toString())},autofocusFields(t=!1){this.$nextTick(()=>{if(t&&document.activeElement&&document.activeElement!==document.body&&this.$el.compareDocumentPosition(document.activeElement)&Node.DOCUMENT_POSITION_PRECEDING)return;let e=this.$el.querySelectorAll(".fi-sc-tabs-tab.fi-active [autofocus]");for(let i of e)if(i.focus(),document.activeElement===i)break})},debouncedUpdateTabsWithinDropdown(){clearTimeout(this.resizeDebounceTimer),this.resizeDebounceTimer=setTimeout(()=>this.updateTabsWithinDropdown(),150)},async updateTabsWithinDropdown(){this.withinDropdownIndex=null,this.withinDropdownMounted=!1,await this.$nextTick();let t=this.$el.querySelector(".fi-tabs"),e=t.querySelector(".fi-tabs-item:last-child"),i=Array.from(t.children).slice(0,-1),a=i.map(s=>s.style.display);i.forEach(s=>s.style.display=""),t.offsetHeight;let l=this.calculateAvailableWidth(t),h=this.calculateContainerGap(t),u=this.calculateDropdownIconWidth(e),b=this.calculateTabItemGap(i[0]),n=this.calculateTabItemPadding(i[0]),c=this.findOverflowIndex(i,l,h,b,n,u);i.forEach((s,o)=>s.style.display=a[o]),c!==-1&&(this.withinDropdownIndex=c),this.withinDropdownMounted=!0},destroy(){this.unsubscribeLivewireHook?.(),this.boundResizeHandler&&window.removeEventListener("resize",this.boundResizeHandler),clearTimeout(this.resizeDebounceTimer)}}}export{v as default};
|
||||
1
public/js/filament/schemas/components/wizard.js
Normal file
1
public/js/filament/schemas/components/wizard.js
Normal file
@ -0,0 +1 @@
|
||||
function p({isSkippable:i,isStepPersistedInQueryString:n,key:r,startStep:o,stepQueryStringKey:h}){return{step:null,init(){this.step=this.getSteps().at(o-1),this.$watch("step",()=>{this.updateQueryString(),this.autofocusFields()}),this.autofocusFields(!0)},async requestNextStep(){await this.$wire.callSchemaComponentMethod(r,"nextStep",{currentStepIndex:this.getStepIndex(this.step)})},goToNextStep(){let t=this.getStepIndex(this.step)+1;t>=this.getSteps().length||(this.step=this.getSteps()[t],this.scroll())},goToPreviousStep(){let t=this.getStepIndex(this.step)-1;t<0||(this.step=this.getSteps()[t],this.scroll())},goToStep(t){let e=this.getStepIndex(t);e<=-1||!i&&e>this.getStepIndex(this.step)||(this.step=t,this.scroll())},scroll(){this.$nextTick(()=>{this.$refs.header?.children[this.getStepIndex(this.step)].scrollIntoView({behavior:"smooth",block:"start"})})},autofocusFields(t=!1){this.$nextTick(()=>{if(t&&document.activeElement&&document.activeElement!==document.body&&this.$el.compareDocumentPosition(document.activeElement)&Node.DOCUMENT_POSITION_PRECEDING)return;let e=this.$refs[`step-${this.step}`]?.querySelectorAll("[autofocus]")??[];for(let s of e)if(s.focus(),document.activeElement===s)break})},getStepIndex(t){let e=this.getSteps().findIndex(s=>s===t);return e===-1?0:e},getSteps(){return JSON.parse(this.$refs.stepsData.value)},isFirstStep(){return this.getStepIndex(this.step)<=0},isLastStep(){return this.getStepIndex(this.step)+1>=this.getSteps().length},isStepAccessible(t){return i||this.getStepIndex(this.step)>this.getStepIndex(t)},updateQueryString(){if(!n)return;let t=new URL(window.location.href);t.searchParams.set(h,this.step),history.replaceState(null,document.title,t.toString())}}}export{p as default};
|
||||
1
public/js/filament/schemas/schemas.js
Normal file
1
public/js/filament/schemas/schemas.js
Normal file
@ -0,0 +1 @@
|
||||
(()=>{var o=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let i=this.$el.parentElement;i&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(i),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let i=this.$el.parentElement;if(!i)return;let e=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=i.offsetWidth+parseInt(e.marginInlineStart,10)*-1+parseInt(e.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});var a=function(i,e,n){let t=i;if(e.startsWith("/")&&(n=!0,e=e.slice(1)),n)return e;for(;e.startsWith("../");)t=t.includes(".")?t.slice(0,t.lastIndexOf(".")):null,e=e.slice(3);return["",null,void 0].includes(t)?e:["",null,void 0].includes(e)?t:`${t}.${e}`},d=i=>{let e=Alpine.findClosest(i,n=>n.__livewire);if(!e)throw"Could not find Livewire component in DOM tree.";return e.__livewire};document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentSchema",({livewireId:i})=>({handleFormValidationError(e){e.detail.livewireId===i&&this.$nextTick(()=>{let n=this.$el.querySelector("[data-validation-error]");if(!n)return;let t=n;for(;t;)t.dispatchEvent(new CustomEvent("expand")),t=t.parentNode;setTimeout(()=>n.closest("[data-field-wrapper]").scrollIntoView({behavior:"smooth",block:"start",inline:"start"}),200)})},isStateChanged(e,n){if(e===void 0)return!1;try{return JSON.stringify(e)!==JSON.stringify(n)}catch{return e!==n}}})),window.Alpine.data("filamentSchemaComponent",({path:i,containerPath:e,$wire:n})=>({$statePath:i,$get:(t,r)=>n.$get(a(e,t,r)),$set:(t,r,s,l=!1)=>n.$set(a(e,t,s),r,l),get $state(){return n.$get(i)}})),window.Alpine.data("filamentActionsSchemaComponent",o),Livewire.interceptMessage(({message:i,onSuccess:e})=>{e(({payload:n})=>{n.effects?.dispatches?.forEach(t=>{if(!t.params?.awaitSchemaComponent)return;let r=Array.from(i.component.el.querySelectorAll(`[wire\\:partial="schema-component::${t.params.awaitSchemaComponent}"]`)).filter(s=>d(s)===i.component);if(r.length!==1){if(r.length>1)throw`Multiple schema components found with key [${t.params.awaitSchemaComponent}].`;window.addEventListener(`schema-component-${component.id}-${t.params.awaitSchemaComponent}-loaded`,()=>{window.dispatchEvent(new CustomEvent(t.name,{detail:t.params}))},{once:!0})}})})})});})();
|
||||
46
public/js/filament/support/support.js
Normal file
46
public/js/filament/support/support.js
Normal file
File diff suppressed because one or more lines are too long
1
public/js/filament/tables/components/columns/checkbox.js
Normal file
1
public/js/filament/tables/components/columns/checkbox.js
Normal file
@ -0,0 +1 @@
|
||||
function a({name:r,recordKey:s,state:n}){return{error:void 0,isLoading:!1,state:n,unsubscribeLivewireHook:null,init(){this.unsubscribeLivewireHook=Livewire.interceptMessage(({message:e,onSuccess:t})=>{t(()=>{this.$nextTick(()=>{if(this.isLoading||e.component.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let i=this.getServerState();i===void 0||Alpine.raw(this.state)===i||(this.state=i)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let t=await this.$wire.updateTableColumnState(r,s,this.state);this.error=t?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)},destroy(){this.unsubscribeLivewireHook?.()}}}export{a as default};
|
||||
11
public/js/filament/tables/components/columns/select.js
Normal file
11
public/js/filament/tables/components/columns/select.js
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
||||
function a({name:i,recordKey:s,state:n}){return{error:void 0,isLoading:!1,state:n,unsubscribeLivewireHook:null,init(){this.unsubscribeLivewireHook=Livewire.interceptMessage(({message:e,onSuccess:t})=>{t(()=>{this.$nextTick(()=>{if(this.isLoading||e.component.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let r=this.getServerState();r===void 0||this.getNormalizedState()===r||(this.state=r)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||this.getNormalizedState()===e)return;this.isLoading=!0;let t=await this.$wire.updateTableColumnState(i,s,this.state);this.error=t?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.getNormalizedState()),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[null,void 0].includes(this.$refs.serverState.value)?"":this.$refs.serverState.value.replaceAll('\\"','"')},getNormalizedState(){let e=Alpine.raw(this.state);return[null,void 0].includes(e)?"":e},destroy(){this.unsubscribeLivewireHook?.()}}}export{a as default};
|
||||
1
public/js/filament/tables/components/columns/toggle.js
Normal file
1
public/js/filament/tables/components/columns/toggle.js
Normal file
@ -0,0 +1 @@
|
||||
function a({name:r,recordKey:s,state:n}){return{error:void 0,isLoading:!1,state:n,unsubscribeLivewireHook:null,init(){this.unsubscribeLivewireHook=Livewire.interceptMessage(({message:e,onSuccess:t})=>{t(()=>{this.$nextTick(()=>{if(this.isLoading||e.component.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let i=this.getServerState();i===void 0||Alpine.raw(this.state)===i||(this.state=i)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let t=await this.$wire.updateTableColumnState(r,s,this.state);this.error=t?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)},destroy(){this.unsubscribeLivewireHook?.()}}}export{a as default};
|
||||
1
public/js/filament/tables/tables.js
Normal file
1
public/js/filament/tables/tables.js
Normal file
File diff suppressed because one or more lines are too long
30
public/js/filament/widgets/components/chart.js
Normal file
30
public/js/filament/widgets/components/chart.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user