diff --git a/app/Livewire/Customer/Plugins/Create.php b/app/Livewire/Customer/Plugins/Create.php
index 5e7656dd..ab90afa9 100644
--- a/app/Livewire/Customer/Plugins/Create.php
+++ b/app/Livewire/Customer/Plugins/Create.php
@@ -31,6 +31,12 @@ class Create extends Component
public bool $reposLoaded = false;
+ #[Computed]
+ public function hasCompletedDeveloperOnboarding(): bool
+ {
+ return auth()->user()->developerAccount?->hasCompletedOnboarding() ?? false;
+ }
+
#[Computed]
public function owners(): array
{
@@ -142,6 +148,12 @@ function ($attribute, $value, $fail): void {
return;
}
+ if ($this->pluginType === 'paid' && ! $this->hasCompletedDeveloperOnboarding) {
+ session()->flash('error', 'You must complete developer onboarding before creating a paid plugin.');
+
+ return;
+ }
+
$repository = trim($this->repository, '/');
$repositoryUrl = 'https://github.com/'.$repository;
[$owner, $repo] = explode('/', $repository);
diff --git a/app/Livewire/Customer/Plugins/Show.php b/app/Livewire/Customer/Plugins/Show.php
index b10163b8..cd614843 100644
--- a/app/Livewire/Customer/Plugins/Show.php
+++ b/app/Livewire/Customer/Plugins/Show.php
@@ -9,6 +9,7 @@
use App\Notifications\PluginSubmitted;
use App\Services\GitHubUserService;
use Illuminate\Support\Facades\Storage;
+use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Attributes\Validate;
@@ -48,6 +49,12 @@ class Show extends Component
public ?string $tier = null;
+ #[Computed]
+ public function hasCompletedDeveloperOnboarding(): bool
+ {
+ return auth()->user()->developerAccount?->hasCompletedOnboarding() ?? false;
+ }
+
public function mount(string $vendor, string $package): void
{
$this->plugin = Plugin::findByVendorPackageOrFail($vendor, $package);
@@ -181,6 +188,12 @@ function (string $attribute, mixed $value, \Closure $fail) {
'tier.required' => 'Please select a pricing tier for your paid plugin.',
]);
+ if ($this->plugin->isDraft() && $this->pluginType === 'paid' && ! $this->hasCompletedDeveloperOnboarding) {
+ session()->flash('error', 'You must complete developer onboarding before setting a plugin as paid.');
+
+ return;
+ }
+
$data = [
'display_name' => $this->displayName ?: null,
'support_channel' => $this->supportChannel,
diff --git a/package-lock.json b/package-lock.json
index c4c8703b..67cf3e51 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,5 +1,5 @@
{
- "name": "lazy-podenco",
+ "name": "hungry-toucan",
"lockfileVersion": 3,
"requires": true,
"packages": {
diff --git a/resources/views/livewire/customer/plugins/create.blade.php b/resources/views/livewire/customer/plugins/create.blade.php
index dddcede4..1ef23b01 100644
--- a/resources/views/livewire/customer/plugins/create.blade.php
+++ b/resources/views/livewire/customer/plugins/create.blade.php
@@ -59,14 +59,19 @@
-
-
-
+
+ hasCompletedDeveloperOnboarding ? '' : 'disabled' }} />
Paid Plugin
Commercial plugin, hosted on plugins.nativephp.com
+ @if (! $this->hasCompletedDeveloperOnboarding)
+
+ To create paid plugins, you need to complete developer onboarding.
+
+ @endif
diff --git a/tests/Feature/Livewire/Customer/PluginPaidOnboardingTest.php b/tests/Feature/Livewire/Customer/PluginPaidOnboardingTest.php
new file mode 100644
index 00000000..faa9288e
--- /dev/null
+++ b/tests/Feature/Livewire/Customer/PluginPaidOnboardingTest.php
@@ -0,0 +1,240 @@
+create([
+ 'github_id' => '12345',
+ 'github_username' => 'testuser',
+ 'github_token' => encrypt('fake-token'),
+ ]);
+ }
+
+ private function fakeComposerJson(string $owner, string $repo, string $packageName): void
+ {
+ $composerJson = base64_encode(json_encode(['name' => $packageName]));
+
+ Http::fake([
+ "api.github.com/repos/{$owner}/{$repo}/contents/composer.json*" => Http::response([
+ 'content' => $composerJson,
+ ]),
+ 'api.github.com/*' => Http::response([], 404),
+ ]);
+ }
+
+ // ========================================
+ // Create: Paid option disabled without onboarding
+ // ========================================
+
+ public function test_create_page_shows_paid_option_disabled_without_onboarding(): void
+ {
+ $user = $this->createGitHubUser();
+
+ Livewire::actingAs($user)->test(Create::class)
+ ->assertSee('complete developer onboarding')
+ ->assertSeeHtml('disabled');
+ }
+
+ public function test_create_page_shows_paid_option_enabled_with_onboarding(): void
+ {
+ $user = $this->createGitHubUser();
+ DeveloperAccount::factory()->for($user)->create();
+
+ Livewire::actingAs($user)->test(Create::class)
+ ->assertDontSee('complete developer onboarding');
+ }
+
+ public function test_create_paid_plugin_blocked_without_onboarding(): void
+ {
+ $user = $this->createGitHubUser();
+
+ $this->fakeComposerJson('testuser', 'my-plugin', 'testuser/my-plugin');
+
+ Livewire::actingAs($user)->test(Create::class)
+ ->set('repository', 'testuser/my-plugin')
+ ->set('pluginType', 'paid')
+ ->call('createPlugin')
+ ->assertNoRedirect();
+
+ $this->assertDatabaseMissing('plugins', [
+ 'repository_url' => 'https://github.com/testuser/my-plugin',
+ ]);
+ }
+
+ public function test_create_paid_plugin_allowed_with_onboarding(): void
+ {
+ $user = $this->createGitHubUser();
+ DeveloperAccount::factory()->for($user)->create();
+
+ $this->fakeComposerJson('testuser', 'paid-plugin', 'testuser/paid-plugin');
+
+ Livewire::actingAs($user)->test(Create::class)
+ ->set('repository', 'testuser/paid-plugin')
+ ->set('pluginType', 'paid')
+ ->call('createPlugin');
+
+ $this->assertDatabaseHas('plugins', [
+ 'repository_url' => 'https://github.com/testuser/paid-plugin',
+ 'type' => 'paid',
+ 'status' => 'draft',
+ ]);
+ }
+
+ public function test_create_free_plugin_allowed_without_onboarding(): void
+ {
+ $user = $this->createGitHubUser();
+
+ $this->fakeComposerJson('testuser', 'free-plugin', 'testuser/free-plugin');
+
+ Livewire::actingAs($user)->test(Create::class)
+ ->set('repository', 'testuser/free-plugin')
+ ->set('pluginType', 'free')
+ ->call('createPlugin');
+
+ $this->assertDatabaseHas('plugins', [
+ 'repository_url' => 'https://github.com/testuser/free-plugin',
+ 'type' => 'free',
+ 'status' => 'draft',
+ ]);
+ }
+
+ // ========================================
+ // Edit Draft: Paid option disabled without onboarding
+ // ========================================
+
+ public function test_edit_draft_shows_paid_option_disabled_without_onboarding(): void
+ {
+ $user = $this->createGitHubUser();
+ $plugin = Plugin::factory()->draft()->for($user)->create([
+ 'name' => 'testuser/onboard-test',
+ ]);
+
+ [$vendor, $package] = explode('/', $plugin->name);
+
+ Livewire::actingAs($user)->test(Show::class, [
+ 'vendor' => $vendor,
+ 'package' => $package,
+ ])
+ ->assertSee('complete developer onboarding')
+ ->assertSeeHtml('disabled');
+ }
+
+ public function test_edit_draft_shows_paid_option_enabled_with_onboarding(): void
+ {
+ $user = $this->createGitHubUser();
+ DeveloperAccount::factory()->for($user)->create();
+ $plugin = Plugin::factory()->draft()->for($user)->create([
+ 'name' => 'testuser/onboard-enabled',
+ ]);
+
+ [$vendor, $package] = explode('/', $plugin->name);
+
+ Livewire::actingAs($user)->test(Show::class, [
+ 'vendor' => $vendor,
+ 'package' => $package,
+ ])
+ ->assertDontSee('complete developer onboarding');
+ }
+
+ public function test_save_draft_as_paid_blocked_without_onboarding(): void
+ {
+ $user = $this->createGitHubUser();
+ $plugin = Plugin::factory()->draft()->for($user)->create([
+ 'name' => 'testuser/save-paid-test',
+ 'support_channel' => 'support@test.io',
+ ]);
+
+ [$vendor, $package] = explode('/', $plugin->name);
+
+ Livewire::actingAs($user)->test(Show::class, [
+ 'vendor' => $vendor,
+ 'package' => $package,
+ ])
+ ->set('description', 'A test plugin')
+ ->set('supportChannel', 'support@test.io')
+ ->set('pluginType', 'paid')
+ ->set('tier', 'gold')
+ ->call('save');
+
+ $plugin->refresh();
+ $this->assertNotEquals(PluginType::Paid, $plugin->type);
+ }
+
+ public function test_save_draft_as_paid_allowed_with_onboarding(): void
+ {
+ $user = $this->createGitHubUser();
+ DeveloperAccount::factory()->for($user)->create();
+ $plugin = Plugin::factory()->draft()->for($user)->create([
+ 'name' => 'testuser/save-paid-ok',
+ 'support_channel' => 'support@test.io',
+ ]);
+
+ [$vendor, $package] = explode('/', $plugin->name);
+
+ Livewire::actingAs($user)->test(Show::class, [
+ 'vendor' => $vendor,
+ 'package' => $package,
+ ])
+ ->set('description', 'A test plugin')
+ ->set('supportChannel', 'support@test.io')
+ ->set('pluginType', 'paid')
+ ->set('tier', 'gold')
+ ->call('save');
+
+ $plugin->refresh();
+ $this->assertEquals(PluginType::Paid, $plugin->type);
+ }
+
+ public function test_create_page_shows_onboarding_link_without_onboarding(): void
+ {
+ $user = $this->createGitHubUser();
+
+ Livewire::actingAs($user)->test(Create::class)
+ ->assertSeeHtml(route('customer.developer.onboarding'));
+ }
+
+ public function test_edit_draft_shows_onboarding_link_without_onboarding(): void
+ {
+ $user = $this->createGitHubUser();
+ $plugin = Plugin::factory()->draft()->for($user)->create([
+ 'name' => 'testuser/link-test',
+ ]);
+
+ [$vendor, $package] = explode('/', $plugin->name);
+
+ Livewire::actingAs($user)->test(Show::class, [
+ 'vendor' => $vendor,
+ 'package' => $package,
+ ])
+ ->assertSeeHtml(route('customer.developer.onboarding'));
+ }
+}
diff --git a/tests/Feature/Livewire/Customer/PluginStatusTransitionsTest.php b/tests/Feature/Livewire/Customer/PluginStatusTransitionsTest.php
index ccf74e6d..1141a406 100644
--- a/tests/Feature/Livewire/Customer/PluginStatusTransitionsTest.php
+++ b/tests/Feature/Livewire/Customer/PluginStatusTransitionsTest.php
@@ -8,6 +8,7 @@
use App\Enums\PluginType;
use App\Features\AllowPaidPlugins;
use App\Livewire\Customer\Plugins\Show;
+use App\Models\DeveloperAccount;
use App\Models\Plugin;
use App\Models\User;
use App\Notifications\PluginSubmitted;
@@ -374,6 +375,7 @@ public function test_save_paid_plugin_requires_tier(): void
Feature::define(AllowPaidPlugins::class, true);
$user = $this->createGitHubUser();
+ DeveloperAccount::factory()->for($user)->create();
$plugin = $this->createDraftPlugin($user);
$this->mountShowComponent($user, $plugin)
@@ -393,6 +395,7 @@ public function test_submit_paid_plugin_with_tier_saves_type_and_tier(): void
Feature::define(AllowPaidPlugins::class, true);
$user = $this->createGitHubUser();
+ DeveloperAccount::factory()->for($user)->create();
$plugin = $this->createDraftPlugin($user);
$this->fakeGitHubForSubmission($plugin);