diff --git a/app/Features/AllowPaidPlugins.php b/app/Features/AllowPaidPlugins.php index 1adc4e6f..d36d38b8 100644 --- a/app/Features/AllowPaidPlugins.php +++ b/app/Features/AllowPaidPlugins.php @@ -2,19 +2,11 @@ namespace App\Features; -use Laravel\Pennant\Feature; +use Stephenjude\FilamentFeatureFlag\Traits\WithFeatureResolver; class AllowPaidPlugins { - /** - * Resolve the feature's initial value. - */ - public function resolve(mixed $scope): bool - { - if ($scope) { - return Feature::for(null)->active(static::class); - } + use WithFeatureResolver; - return false; - } -} + protected bool $defaultValue = false; +} \ No newline at end of file diff --git a/app/Features/ShowAuthButtons.php b/app/Features/ShowAuthButtons.php index e7ae24bc..c535b713 100644 --- a/app/Features/ShowAuthButtons.php +++ b/app/Features/ShowAuthButtons.php @@ -2,19 +2,11 @@ namespace App\Features; -use Laravel\Pennant\Feature; +use Stephenjude\FilamentFeatureFlag\Traits\WithFeatureResolver; class ShowAuthButtons { - /** - * Resolve the feature's initial value. - */ - public function resolve(mixed $scope): bool - { - if ($scope) { - return Feature::for(null)->active(static::class); - } + use WithFeatureResolver; - return false; - } + protected bool $defaultValue = false; } diff --git a/app/Features/ShowPlugins.php b/app/Features/ShowPlugins.php index 56327249..32caa49e 100644 --- a/app/Features/ShowPlugins.php +++ b/app/Features/ShowPlugins.php @@ -2,19 +2,11 @@ namespace App\Features; -use Laravel\Pennant\Feature; +use Stephenjude\FilamentFeatureFlag\Traits\WithFeatureResolver; class ShowPlugins { - /** - * Resolve the feature's initial value. - */ - public function resolve(mixed $scope): bool - { - if ($scope) { - return Feature::for(null)->active(static::class); - } + use WithFeatureResolver; - return false; - } -} + protected bool $defaultValue = false; +} \ No newline at end of file diff --git a/app/Filament/Resources/PluginResource.php b/app/Filament/Resources/PluginResource.php index a1d9bcc9..c727cdd3 100644 --- a/app/Filament/Resources/PluginResource.php +++ b/app/Filament/Resources/PluginResource.php @@ -41,32 +41,29 @@ public static function form(Form $form): Form ->visible(fn (?Plugin $record) => $record !== null), Forms\Components\TextInput::make('name') - ->label('Composer Package Name') - ->disabled(), + ->label('Composer Package Name'), Forms\Components\Select::make('type') - ->options(PluginType::class) - ->disabled(), + ->options(PluginType::class), Forms\Components\TextInput::make('repository_url') ->label('Repository URL') - ->disabled() + ->url() ->suffixIcon('heroicon-o-arrow-top-right-on-square') ->suffixIconColor('gray'), Forms\Components\Select::make('status') - ->options(PluginStatus::class) - ->disabled(), + ->options(PluginStatus::class), Forms\Components\Textarea::make('description') ->label('Description') - ->disabled() + ->columnSpanFull(), Forms\Components\Textarea::make('rejection_reason') ->label('Rejection Reason') - ->disabled() + ->visible(fn (?Plugin $record) => $record?->isRejected()), ]) ->columns(2), @@ -75,19 +72,19 @@ public static function form(Form $form): Form ->schema([ Forms\Components\Select::make('user_id') ->relationship('user', 'email') - ->disabled(), + ->searchable() + ->preload(), Forms\Components\DateTimePicker::make('created_at') - ->label('Submitted At') - ->disabled(), + ->label('Submitted At'), Forms\Components\Select::make('approved_by') ->relationship('approvedBy', 'email') - ->disabled() + ->visible(fn (?Plugin $record) => $record?->approved_by !== null), Forms\Components\DateTimePicker::make('approved_at') - ->disabled() + ->visible(fn (?Plugin $record) => $record?->approved_at !== null), ]) ->columns(2), @@ -156,6 +153,7 @@ public static function table(Table $table): Table ->label('Active'), ]) ->actions([ + Tables\Actions\EditAction::make(), // Approve Action Tables\Actions\Action::make('approve') ->icon('heroicon-o-check') @@ -263,6 +261,7 @@ public static function getPages(): array { return [ 'index' => Pages\ListPlugins::route('/'), + 'edit' => Pages\EditPlugin::route('/{record}/edit'), 'view' => Pages\ViewPlugin::route('/{record}'), ]; } diff --git a/app/Filament/Resources/PluginResource/Pages/EditPlugin.php b/app/Filament/Resources/PluginResource/Pages/EditPlugin.php new file mode 100644 index 00000000..908080ad --- /dev/null +++ b/app/Filament/Resources/PluginResource/Pages/EditPlugin.php @@ -0,0 +1,12 @@ + $this->handleSubscriptionCreated(), Invoice::BILLING_REASON_SUBSCRIPTION_UPDATE => null, // TODO: Handle subscription update Invoice::BILLING_REASON_SUBSCRIPTION_CYCLE => $this->handleSubscriptionRenewal(), + Invoice::BILLING_REASON_MANUAL => null, default => null, }; } diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index c2bdcb83..cde11acd 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -10,6 +10,7 @@ use Filament\Panel; use Filament\PanelProvider; use Filament\Support\Colors\Color; +use Stephenjude\FilamentFeatureFlag\FeatureFlagPlugin; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Cookie\Middleware\EncryptCookies; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken; @@ -42,6 +43,9 @@ public function panel(Panel $panel): Panel ->widgets([ // ]) + ->plugins([ + FeatureFlagPlugin::make(), + ]) ->middleware([ EncryptCookies::class, AddQueuedCookiesToResponse::class, diff --git a/composer.json b/composer.json index 85864f4f..e37b1c9b 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ "php": "^8.1", "artesaos/seotools": "^1.2", "blade-ui-kit/blade-heroicons": "^2.3", + "codeat3/blade-vaadin-icons": "^1.6", "doctrine/dbal": "^3.9", "embed/embed": "^4.4", "filament/filament": "^3.2", @@ -29,6 +30,7 @@ "simonhamp/the-og": "^0.7.0", "spatie/laravel-menu": "^4.1", "spatie/yaml-front-matter": "^2.0", + "stephenjude/filament-feature-flags": "^3.0", "symfony/http-client": "^7.2", "symfony/mailgun-mailer": "^7.1", "torchlight/torchlight-commonmark": "^0.5.5" diff --git a/composer.lock b/composer.lock index 5fea3c3d..6f07eed8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "af06c7ca6c76ed93b6fd1145f77cf32a", + "content-hash": "8c63c423e469ed2a8fef7a9f536e624c", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -573,6 +573,77 @@ ], "time": "2023-12-11T17:09:12+00:00" }, + { + "name": "codeat3/blade-vaadin-icons", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/codeat3/blade-vaadin-icons.git", + "reference": "eb9b6e7ddfdf1746246ae0f3d8608a7a9e4d4411" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/codeat3/blade-vaadin-icons/zipball/eb9b6e7ddfdf1746246ae0f3d8608a7a9e4d4411", + "reference": "eb9b6e7ddfdf1746246ae0f3d8608a7a9e4d4411", + "shasum": "" + }, + "require": { + "blade-ui-kit/blade-icons": "^1.1", + "illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "codeat3/blade-icon-generation-helpers": "^0.10", + "codeat3/phpcs-styles": "^1.0", + "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0|^10.0", + "phpunit/phpunit": "^9.0|^10.5|^11.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Codeat3\\BladeVaadinIcons\\BladeVaadinIconsServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Codeat3\\BladeVaadinIcons\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Swapnil Sarwe", + "homepage": "https://swapnilsarwe.com" + }, + { + "name": "Dries Vints", + "homepage": "https://driesvints.com" + } + ], + "description": "A package to easily make use of \"Vaadin Icons\" in your Laravel Blade views.", + "homepage": "https://github.com/codeat3/blade-vaadin-icons", + "keywords": [ + "blade", + "laravel", + "vaadin-icons" + ], + "support": { + "issues": "https://github.com/codeat3/blade-vaadin-icons/issues", + "source": "https://github.com/codeat3/blade-vaadin-icons/tree/1.6.0" + }, + "funding": [ + { + "url": "https://github.com/swapnilsarwe", + "type": "github" + } + ], + "time": "2025-02-25T09:46:58+00:00" + }, { "name": "composer/ca-bundle", "version": "1.5.10", @@ -7319,6 +7390,74 @@ ], "time": "2025-11-24T16:17:28+00:00" }, + { + "name": "stephenjude/filament-feature-flags", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/stephenjude/filament-feature-flags.git", + "reference": "98fe81567e27ccfabba205abf3f0059a9dde52ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stephenjude/filament-feature-flags/zipball/98fe81567e27ccfabba205abf3f0059a9dde52ab", + "reference": "98fe81567e27ccfabba205abf3f0059a9dde52ab", + "shasum": "" + }, + "require": { + "filament/filament": "^3.0", + "illuminate/contracts": "^10.0|^11.0|^12.0", + "laravel/pennant": "^1.10", + "php": "^8.2", + "spatie/laravel-package-tools": "^1.14.0" + }, + "require-dev": { + "laravel/pint": "^1.0", + "nunomaduro/collision": "^7.8", + "orchestra/testbench": "^8.8|^10.0", + "pestphp/pest": "^2.20|^3.7", + "pestphp/pest-plugin-arch": "^2.0|^3.0", + "pestphp/pest-plugin-laravel": "^2.0|^3.1", + "pestphp/pest-plugin-livewire": "^2.1|^3.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Stephenjude\\FilamentFeatureFlag\\FeatureFlagPluginServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Stephenjude\\FilamentFeatureFlag\\": "src/", + "Stephenjude\\FilamentFeatureFlag\\Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "stephenjude", + "email": "stephenjudesuccess@gmail.com", + "role": "Developer" + } + ], + "description": "Filament implementation of feature flags and segmentation with Laravel Pennant.", + "homepage": "https://github.com/stephenjude/filament-feature-flags", + "keywords": [ + "filament-feature-flags", + "laravel", + "stephenjude" + ], + "support": { + "issues": "https://github.com/stephenjude/filament-feature-flags/issues", + "source": "https://github.com/stephenjude/filament-feature-flags/tree/3.0.4" + }, + "time": "2025-08-12T08:13:54+00:00" + }, { "name": "stripe/stripe-php", "version": "v16.6.0", diff --git a/config/filament-feature-flags.php b/config/filament-feature-flags.php new file mode 100644 index 00000000..bf2cc8b7 --- /dev/null +++ b/config/filament-feature-flags.php @@ -0,0 +1,69 @@ + true, + + /* + * Default scope: User::class, Team::class + */ + 'scope' => App\Models\User::class, + + /* + * Column names and data source that can be used to activate or deactivate for a segment of users. + * This columns must exist on the users table and the data source must be a model. + * COLUMN: The column name as defined on the default scope model config. + * MODEL: The eloquent model of the source table. + * VALUE: The column to be used as value. + * KEY: The column to be used as key. + */ + 'segments' => [ + [ + 'column' => 'email', + 'source' => [ + 'model' => App\Models\User::class, + 'value' => 'email', + 'key' => 'email', + ], + ], + // [ + // 'column' => 'currency', + // 'source' => [ + // 'model' => Squire\Models\Currency::class, // composer require squirephp/currencies-en + // 'value' => 'code_alphabetic', + // 'key' => 'code_alphabetic', + // ], + // ], + ], + + 'panel' => [ + /* + * Navigation group for admin panel resource. + */ + 'group' => 'Settings', + + /* + * Navigation item label for admin panel resource. + */ + 'label' => 'Manage Features', + + /* + * Resource title for admin panel resource. + */ + 'title' => 'Manage Features & Segments', + + /* + * Navigation item icon for admin panel resource. + */ + 'icon' => 'heroicon-o-cursor-arrow-ripple', + ], + + 'resources' => [ + Stephenjude\FilamentFeatureFlag\Resources\FeatureSegmentResource::class, + ], +]; diff --git a/database/migrations/2026_01_19_003725_create_filament_feature_flags_table.php b/database/migrations/2026_01_19_003725_create_filament_feature_flags_table.php new file mode 100644 index 00000000..c7a1a88b --- /dev/null +++ b/database/migrations/2026_01_19_003725_create_filament_feature_flags_table.php @@ -0,0 +1,22 @@ +id(); + $table->string('feature'); + $table->string('scope'); + $table->json('values'); + $table->boolean('active'); + $table->timestamps(); + + $table->unique(['feature', 'scope', 'active']); + }); + } +}; diff --git a/package-lock.json b/package-lock.json index 2fcbd77d..abb3f776 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "nativephp.com", + "name": "nativephp", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/resources/views/cart/show.blade.php b/resources/views/cart/show.blade.php index 59eaf975..08c0b058 100644 --- a/resources/views/cart/show.blade.php +++ b/resources/views/cart/show.blade.php @@ -186,7 +186,7 @@ class="flex gap-4 p-6" {{ $item->plugin->name }} @else
- +
@endif diff --git a/resources/views/cart/success.blade.php b/resources/views/cart/success.blade.php index 11caa701..cd82a24a 100644 --- a/resources/views/cart/success.blade.php +++ b/resources/views/cart/success.blade.php @@ -73,7 +73,7 @@ class="rounded-lg border border-gray-200 bg-white p-8 text-center dark:border-gr
- +
diff --git a/resources/views/components/plugin-card.blade.php b/resources/views/components/plugin-card.blade.php index a673243b..917f7a1e 100644 --- a/resources/views/components/plugin-card.blade.php +++ b/resources/views/components/plugin-card.blade.php @@ -13,7 +13,7 @@ class="size-12 shrink-0 rounded-xl object-cover" /> @else
- +
@endif @if ($plugin->isPaid()) diff --git a/resources/views/customer/developer/dashboard.blade.php b/resources/views/customer/developer/dashboard.blade.php index 42d2e48b..513ec79d 100644 --- a/resources/views/customer/developer/dashboard.blade.php +++ b/resources/views/customer/developer/dashboard.blade.php @@ -160,7 +160,7 @@ @empty
- +

No plugins yet

Submit your first plugin diff --git a/resources/views/customer/developer/onboarding.blade.php b/resources/views/customer/developer/onboarding.blade.php index 3e6476ba..8e5e2960 100644 --- a/resources/views/customer/developer/onboarding.blade.php +++ b/resources/views/customer/developer/onboarding.blade.php @@ -11,7 +11,7 @@

- + My Plugins
diff --git a/resources/views/customer/licenses/index.blade.php b/resources/views/customer/licenses/index.blade.php index 4351b7e2..47ddfe74 100644 --- a/resources/views/customer/licenses/index.blade.php +++ b/resources/views/customer/licenses/index.blade.php @@ -16,7 +16,7 @@ @feature(App\Features\ShowPlugins::class) - + Plugins @endfeature @@ -112,7 +112,7 @@ class="rounded bg-indigo-600 px-2.5 py-1.5 text-xs font-medium text-white hover: {{ $pluginLicense->plugin->name }} @else
- +
@endif diff --git a/resources/views/customer/plugins/index.blade.php b/resources/views/customer/plugins/index.blade.php index 619ddd0d..5817ddf1 100644 --- a/resources/views/customer/plugins/index.blade.php +++ b/resources/views/customer/plugins/index.blade.php @@ -49,7 +49,7 @@ {{-- Browse Plugins Card --}}
- +

Browse Plugins

diff --git a/resources/views/customer/plugins/show.blade.php b/resources/views/customer/plugins/show.blade.php index 90592fce..a6d0d8ea 100644 --- a/resources/views/customer/plugins/show.blade.php +++ b/resources/views/customer/plugins/show.blade.php @@ -44,7 +44,7 @@ {{ $plugin->name }} logo @else

- +
@endif
diff --git a/resources/views/livewire/plugin-directory.blade.php b/resources/views/livewire/plugin-directory.blade.php index 150a86c5..98a5522a 100644 --- a/resources/views/livewire/plugin-directory.blade.php +++ b/resources/views/livewire/plugin-directory.blade.php @@ -99,7 +99,7 @@ class="ml-0.5 rounded-full p-0.5 hover:bg-gray-200 dark:hover:bg-gray-600" @endif @else
- +

No plugins found

@if ($search || $authorUser)

diff --git a/resources/views/plugin-show.blade.php b/resources/views/plugin-show.blade.php index 934ba434..2b8d520d 100644 --- a/resources/views/plugin-show.blade.php +++ b/resources/views/plugin-show.blade.php @@ -60,7 +60,7 @@ class="size-16 shrink-0 rounded-2xl object-cover" /> @else

- +
@endif
diff --git a/resources/views/plugins.blade.php b/resources/views/plugins.blade.php index 791b0c89..e5d213bc 100644 --- a/resources/views/plugins.blade.php +++ b/resources/views/plugins.blade.php @@ -24,7 +24,7 @@ " >
- +
@@ -112,7 +112,7 @@ class="mt-8 flex w-full flex-col items-center justify-center gap-4 sm:flex-row" href="{{ route('plugins.directory') }}" class="flex items-center justify-center gap-2.5 rounded-xl bg-zinc-800 px-6 py-4 text-white transition duration-200 hover:bg-zinc-900 dark:bg-indigo-700/80 dark:hover:bg-indigo-900" > - + Browse Plugins
@@ -166,19 +166,19 @@ class="flex items-center justify-center gap-2.5 rounded-xl bg-gray-200 px-6 py-4 @empty
- +

Featured plugins coming soon

diff --git a/resources/views/plugins/purchase-success.blade.php b/resources/views/plugins/purchase-success.blade.php index a1e98dc1..592e4649 100644 --- a/resources/views/plugins/purchase-success.blade.php +++ b/resources/views/plugins/purchase-success.blade.php @@ -71,7 +71,7 @@ class="rounded-lg border border-gray-200 bg-white p-8 text-center dark:border-gr {{ $plugin->name }} @else
- +
@endif {{ $plugin->name }} diff --git a/resources/views/plugins/purchase.blade.php b/resources/views/plugins/purchase.blade.php index a5c80f31..21bc4656 100644 --- a/resources/views/plugins/purchase.blade.php +++ b/resources/views/plugins/purchase.blade.php @@ -21,7 +21,7 @@ class="inline-flex items-center gap-2 opacity-60 transition duration-200 hover:- {{-- Plugin icon and title --}}
- +

diff --git a/routes/web.php b/routes/web.php index 114bda97..0202ccfb 100644 --- a/routes/web.php +++ b/routes/web.php @@ -63,11 +63,9 @@ Route::view('build-my-app', 'build-my-app')->name('build-my-app'); // Public plugin directory routes -Route::middleware(EnsureFeaturesAreActive::using(ShowPlugins::class))->group(function () { Route::get('plugins', [PluginDirectoryController::class, 'index'])->name('plugins'); Route::get('plugins/directory', App\Livewire\PluginDirectory::class)->name('plugins.directory'); Route::get('plugins/{plugin}', [PluginDirectoryController::class, 'show'])->name('plugins.show'); -}); Route::view('sponsor', 'sponsoring')->name('sponsoring'); Route::view('vs-react-native-expo', 'vs-react-native-expo')->name('vs-react-native-expo');