Skip to content

Commit 5fa00ce

Browse files
simonhampclaude
andauthored
Rename paid plugin badge to Premium and revamp install instructions (#413)
Change the marketplace plugin card badge from "Paid" to "Premium" (display string only). On the plugin show page, restructure the premium install box into a collapsible "Installation Instructions" section (collapsed by default) containing a nested "Configure Composer" collapsible and a single install command block (vendor:publish, composer require, native:plugin:register), linking to the Using Plugins guide. Extract a reusable x-plugin-command component for the copyable command boxes. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 20c9d95 commit 5fa00ce

6 files changed

Lines changed: 158 additions & 67 deletions

File tree

resources/views/components/plugin-card.blade.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class="size-12 shrink-0 rounded-xl object-cover"
2626
</span>
2727
@elseif ($plugin->isPaid())
2828
<span class="inline-flex items-center rounded-full bg-emerald-100 px-2.5 py-0.5 text-xs font-medium text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-400">
29-
Paid
29+
Premium
3030
</span>
3131
@else
3232
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-800 dark:bg-gray-700 dark:text-gray-300">
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
@props(['command'])
2+
3+
<div class="flex items-center gap-2 rounded-lg bg-zinc-900 dark:bg-zinc-800">
4+
<div class="min-w-0 flex-1 overflow-x-auto p-3">
5+
<code class="block whitespace-pre font-mono text-xs text-zinc-100">{{ $slot->isEmpty() ? $command : $slot }}</code>
6+
</div>
7+
<button
8+
type="button"
9+
x-data="{ copied: false }"
10+
x-on:click="navigator.clipboard.writeText(@js($command)).then(() => { copied = true; setTimeout(() => copied = false, 2000) })"
11+
class="flex shrink-0 items-center self-stretch px-3 text-zinc-400 hover:text-zinc-200"
12+
title="Copy command"
13+
>
14+
<x-heroicon-o-clipboard x-show="!copied" class="size-4" />
15+
<x-heroicon-o-check-circle x-show="copied" x-cloak class="size-4 text-green-400" />
16+
</button>
17+
</div>

resources/views/plugin-show.blade.php

Lines changed: 96 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -114,77 +114,108 @@ class="text-2xl font-bold sm:text-3xl"
114114
@endif
115115

116116
@if ($plugin->isPaid())
117-
<aside class="mb-6 rounded-2xl border border-indigo-200 bg-indigo-50 p-5 dark:border-indigo-800 dark:bg-indigo-950/30">
118-
<h3 class="text-sm font-semibold text-indigo-900 dark:text-indigo-200">Installing this plugin</h3>
119-
<p class="mt-1 text-sm text-indigo-800 dark:text-indigo-300">
120-
Premium plugins require Composer to be configured with the NativePHP plugin repository and your credentials.
121-
</p>
122-
<div class="mt-3 space-y-2">
123-
<div class="flex items-center gap-2 rounded-lg bg-zinc-900 dark:bg-zinc-800">
124-
<div class="min-w-0 flex-1 overflow-x-auto p-3">
125-
<code class="block whitespace-pre font-mono text-xs text-zinc-100">composer config repositories.nativephp-plugins composer https://plugins.nativephp.com</code>
126-
</div>
117+
<aside
118+
x-data="{ open: false }"
119+
class="mb-6 rounded-2xl border border-indigo-200 bg-indigo-50 p-5 dark:border-indigo-800 dark:bg-indigo-950/30"
120+
>
121+
<h3 class="text-sm font-semibold text-indigo-900 dark:text-indigo-200">
122+
<button
123+
type="button"
124+
x-on:click="open = !open"
125+
:aria-expanded="open"
126+
class="flex w-full items-center justify-between gap-2 text-left"
127+
>
128+
Installation Instructions
129+
<svg
130+
xmlns="http://www.w3.org/2000/svg"
131+
fill="none"
132+
viewBox="0 0 24 24"
133+
stroke-width="2"
134+
stroke="currentColor"
135+
class="size-4 shrink-0 text-indigo-500 transition-transform duration-200 dark:text-indigo-400"
136+
:class="{ 'rotate-180': open }"
137+
aria-hidden="true"
138+
>
139+
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
140+
</svg>
141+
</button>
142+
</h3>
143+
144+
<div x-show="open" x-collapse x-cloak>
145+
<p class="mt-3 text-sm text-indigo-800 dark:text-indigo-300">
146+
Premium plugins are served from the NativePHP plugins repository. Configure Composer with your credentials, then install and register the plugin.
147+
</p>
148+
149+
<div
150+
x-data="{ open: false }"
151+
class="mt-4"
152+
>
127153
<button
128154
type="button"
129-
x-data="{ copied: false }"
130-
x-on:click="navigator.clipboard.writeText('composer config repositories.nativephp-plugins composer https://plugins.nativephp.com').then(() => { copied = true; setTimeout(() => copied = false, 2000) })"
131-
class="shrink-0 self-stretch px-3 text-zinc-400 hover:text-zinc-200"
132-
title="Copy command"
155+
x-on:click="open = !open"
156+
:aria-expanded="open"
157+
class="flex w-full items-center justify-between gap-2 text-left"
133158
>
134-
<x-heroicon-o-clipboard x-show="!copied" class="size-4" />
135-
<x-heroicon-o-check-circle x-show="copied" x-cloak class="size-4 text-green-400" />
136-
</button>
137-
</div>
138-
@auth
139-
@php
140-
$pluginCredentialUser = auth()->user();
141-
$pluginCredentialEmail = $pluginCredentialUser->email;
142-
$pluginCredentialKey = $pluginCredentialUser->getPluginLicenseKey();
143-
[$pluginCredentialEmailLocal, $pluginCredentialEmailDomain] = array_pad(explode('@', $pluginCredentialEmail, 2), 2, '');
144-
$maskedPluginCredentialEmail = mb_substr($pluginCredentialEmailLocal, 0, 1).str_repeat('', 6).($pluginCredentialEmailDomain !== '' ? '@'.$pluginCredentialEmailDomain : '');
145-
$maskedPluginCredentialKey = str_repeat('', 16);
146-
@endphp
147-
<div class="flex items-center gap-2 rounded-lg bg-zinc-900 dark:bg-zinc-800">
148-
<div class="min-w-0 flex-1 overflow-x-auto p-3">
149-
<code class="block whitespace-pre font-mono text-xs text-zinc-100">composer config http-basic.plugins.nativephp.com {{ $maskedPluginCredentialEmail }} {{ $maskedPluginCredentialKey }}</code>
150-
</div>
151-
<button
152-
type="button"
153-
x-data="{ copied: false }"
154-
x-on:click="navigator.clipboard.writeText('composer config http-basic.plugins.nativephp.com {{ $pluginCredentialEmail }} {{ $pluginCredentialKey }}').then(() => { copied = true; setTimeout(() => copied = false, 2000) })"
155-
class="shrink-0 self-stretch px-3 text-zinc-400 hover:text-zinc-200"
156-
title="Copy command"
157-
>
158-
<x-heroicon-o-clipboard x-show="!copied" class="size-4" />
159-
<x-heroicon-o-check-circle x-show="copied" x-cloak class="size-4 text-green-400" />
160-
</button>
161-
</div>
162-
@else
163-
<div class="flex items-center gap-2 rounded-lg bg-zinc-900 dark:bg-zinc-800">
164-
<div class="min-w-0 flex-1 overflow-x-auto p-3">
165-
<code class="block whitespace-pre font-mono text-xs text-zinc-100">composer config http-basic.plugins.nativephp.com <span class="text-zinc-400">your-email@example.com</span> <span class="text-zinc-400">your-license-key</span></code>
166-
</div>
167-
<button
168-
type="button"
169-
x-data="{ copied: false }"
170-
x-on:click="navigator.clipboard.writeText('composer config http-basic.plugins.nativephp.com your-email@example.com your-license-key').then(() => { copied = true; setTimeout(() => copied = false, 2000) })"
171-
class="shrink-0 self-stretch px-3 text-zinc-400 hover:text-zinc-200"
172-
title="Copy command"
159+
<span class="text-xs font-semibold text-indigo-900 dark:text-indigo-200">Configure Composer</span>
160+
<svg
161+
xmlns="http://www.w3.org/2000/svg"
162+
fill="none"
163+
viewBox="0 0 24 24"
164+
stroke-width="2"
165+
stroke="currentColor"
166+
class="size-4 shrink-0 text-indigo-500 transition-transform duration-200 dark:text-indigo-400"
167+
:class="{ 'rotate-180': open }"
168+
aria-hidden="true"
173169
>
174-
<x-heroicon-o-clipboard x-show="!copied" class="size-4" />
175-
<x-heroicon-o-check-circle x-show="copied" x-cloak class="size-4 text-green-400" />
176-
</button>
170+
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
171+
</svg>
172+
</button>
173+
174+
<div x-show="open" x-collapse x-cloak class="mt-3 space-y-2">
175+
<p class="text-xs text-indigo-800 dark:text-indigo-300">
176+
A one-time Composer setup so you can install premium plugins.
177+
</p>
178+
@auth
179+
@php
180+
$pluginCredentialUser = auth()->user();
181+
$pluginCredentialEmail = $pluginCredentialUser->email;
182+
$pluginCredentialKey = $pluginCredentialUser->getPluginLicenseKey();
183+
[$pluginCredentialEmailLocal, $pluginCredentialEmailDomain] = array_pad(explode('@', $pluginCredentialEmail, 2), 2, '');
184+
$maskedPluginCredentialEmail = mb_substr($pluginCredentialEmailLocal, 0, 1).str_repeat('', 6).($pluginCredentialEmailDomain !== '' ? '@'.$pluginCredentialEmailDomain : '');
185+
$maskedPluginCredentialKey = str_repeat('', 16);
186+
$pluginRepositoryCommand = 'composer config repositories.nativephp-plugins composer https://plugins.nativephp.com';
187+
$pluginConfigCommand = $pluginRepositoryCommand."\n".'composer config http-basic.plugins.nativephp.com '.$pluginCredentialEmail.' '.$pluginCredentialKey;
188+
$pluginConfigDisplay = e($pluginRepositoryCommand)."\n".'composer config http-basic.plugins.nativephp.com '.e($maskedPluginCredentialEmail).' '.e($maskedPluginCredentialKey);
189+
@endphp
190+
<x-plugin-command :command="$pluginConfigCommand">{!! $pluginConfigDisplay !!}</x-plugin-command>
191+
@else
192+
@php
193+
$pluginRepositoryCommand = 'composer config repositories.nativephp-plugins composer https://plugins.nativephp.com';
194+
$pluginConfigCommand = $pluginRepositoryCommand."\n".'composer config http-basic.plugins.nativephp.com your-email@example.com your-license-key';
195+
$pluginConfigDisplay = e($pluginRepositoryCommand)."\n".'composer config http-basic.plugins.nativephp.com <span class="text-zinc-400">your-email@example.com</span> <span class="text-zinc-400">your-license-key</span>';
196+
@endphp
197+
<x-plugin-command :command="$pluginConfigCommand">{!! $pluginConfigDisplay !!}</x-plugin-command>
198+
@endauth
199+
<p class="text-xs text-indigo-700 dark:text-indigo-400">
200+
@auth
201+
Manage your credentials on your <a href="{{ route('customer.purchased-plugins.index') }}" class="font-medium underline hover:no-underline">Purchased Plugins</a> dashboard.
202+
@else
203+
<a href="{{ route('customer.login') }}" class="font-medium underline hover:no-underline">Log in</a> to see your credentials, or find them on your <a href="{{ route('customer.purchased-plugins.index') }}" class="font-medium underline hover:no-underline">Purchased Plugins</a> dashboard.
204+
@endauth
205+
</p>
177206
</div>
178-
@endauth
207+
</div>
208+
209+
@php
210+
$pluginInstallCommands = "php artisan vendor:publish --tag=nativephp-plugins-provider\ncomposer require {$plugin->name}\nphp artisan native:plugin:register {$plugin->name}";
211+
@endphp
212+
<div class="mt-4 space-y-1.5 border-t border-indigo-200 pt-4 dark:border-indigo-800">
213+
<x-plugin-command :command="$pluginInstallCommands" />
214+
<p class="text-xs text-indigo-700 dark:text-indigo-400">
215+
Full walkthrough in the <a href="{{ url('docs/mobile/3/plugins/using-plugins') }}" class="font-medium underline hover:no-underline">Using Plugins guide &rarr;</a>
216+
</p>
217+
</div>
179218
</div>
180-
<p class="mt-3 text-xs text-indigo-700 dark:text-indigo-400">
181-
@auth
182-
Manage your credentials on your <a href="{{ route('customer.purchased-plugins.index') }}" class="font-medium underline hover:no-underline">Purchased Plugins</a> dashboard.
183-
@else
184-
<a href="{{ route('customer.login') }}" class="font-medium underline hover:no-underline">Log in</a> to see your credentials, or find them on your <a href="{{ route('customer.purchased-plugins.index') }}" class="font-medium underline hover:no-underline">Purchased Plugins</a> dashboard.
185-
@endauth
186-
<a href="{{ url('docs/mobile/3/plugins/using-plugins') }}" class="font-medium underline hover:no-underline">Learn more &rarr;</a>
187-
</p>
188219
</aside>
189220
@endif
190221

tests/Feature/PluginDirectoryTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,13 @@ public function test_plugin_directory_paginates_twelve_per_page(): void
3131
&& $plugins->lastPage() === 2;
3232
});
3333
}
34+
35+
public function test_paid_plugin_card_shows_premium_badge(): void
36+
{
37+
Plugin::factory()->approved()->paid()->create();
38+
39+
Livewire::test(PluginDirectory::class)
40+
->assertSee('Premium')
41+
->assertDontSee('Paid');
42+
}
3443
}

tests/Feature/PluginShowInstallCredentialsTest.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,38 @@ public function test_copy_command_still_contains_the_real_credentials(): void
5353
->assertSee('developer@example.test', false)
5454
->assertSee($key, false);
5555
}
56+
57+
public function test_install_commands_are_shown(): void
58+
{
59+
$plugin = $this->paidPlugin();
60+
61+
$this->get(route('plugins.show', $plugin->routeParams()))
62+
->assertStatus(200)
63+
->assertSee('php artisan vendor:publish --tag=nativephp-plugins-provider')
64+
->assertSee('composer require '.$plugin->name)
65+
->assertSee('php artisan native:plugin:register '.$plugin->name);
66+
}
67+
68+
public function test_credentials_section_is_collapsible_and_still_renders_credentials(): void
69+
{
70+
$user = User::factory()->create(['email' => 'developer@example.test']);
71+
$key = $user->getPluginLicenseKey();
72+
$plugin = $this->paidPlugin();
73+
74+
$this->actingAs($user)
75+
->get(route('plugins.show', $plugin->routeParams()))
76+
->assertStatus(200)
77+
->assertSee('Configure Composer')
78+
->assertSee($key, false);
79+
}
80+
81+
public function test_install_box_links_to_the_using_plugins_guide(): void
82+
{
83+
$plugin = $this->paidPlugin();
84+
85+
$this->get(route('plugins.show', $plugin->routeParams()))
86+
->assertStatus(200)
87+
->assertSee(url('docs/mobile/3/plugins/using-plugins'), false)
88+
->assertSee('Using Plugins guide');
89+
}
5690
}

tests/Feature/PluginShowPaidGuestTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function test_paid_plugin_show_page_renders_for_guest(): void
3131

3232
$this->get(route('plugins.show', $plugin->routeParams()))
3333
->assertStatus(200)
34-
->assertSee('Installing this plugin')
34+
->assertSee('Installation Instructions')
3535
->assertSee('Log in');
3636
}
3737
}

0 commit comments

Comments
 (0)