Skip to content

Commit 6085793

Browse files
simonhampclaude
andauthored
Add developer account details and plugins tab to admin user view (#346)
Show Stripe Connect account info, onboarding status, and plugin terms when viewing a developer user in admin. Add Developer Plugins relation manager tab and a developer indicator column on the users index table. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 10e1aa7 commit 6085793

File tree

3 files changed

+212
-0
lines changed

3 files changed

+212
-0
lines changed

app/Filament/Resources/UserResource.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Filament\Resources;
44

5+
use App\Enums\StripeConnectStatus;
56
use App\Filament\Resources\UserResource\Pages;
67
use App\Filament\Resources\UserResource\RelationManagers;
78
use App\Models\User;
@@ -12,6 +13,7 @@
1213
use Filament\Schemas\Schema;
1314
use Filament\Tables;
1415
use Filament\Tables\Table;
16+
use Illuminate\Support\HtmlString;
1517
use STS\FilamentImpersonate\Actions\Impersonate;
1618

1719
class UserResource extends Resource
@@ -62,6 +64,46 @@ public static function form(Schema $schema): Schema
6264
->maxLength(255)
6365
->disabled(),
6466
]),
67+
Schemas\Components\Section::make('Developer Account')
68+
->inlineLabel()
69+
->columns(1)
70+
->visible(fn (?User $record) => $record?->developerAccount !== null)
71+
->schema([
72+
Forms\Components\Select::make('developerAccount.stripe_connect_status')
73+
->label('Stripe Connect Status')
74+
->options(StripeConnectStatus::class)
75+
->disabled(),
76+
Forms\Components\Placeholder::make('developerAccount.stripe_connect_account_id')
77+
->label('Stripe Connect Account')
78+
->content(fn (User $record) => new HtmlString(
79+
'<a href="https://dashboard.stripe.com/connect/accounts/'
80+
.e($record->developerAccount->stripe_connect_account_id)
81+
.'" target="_blank" class="text-primary-600 hover:underline">'
82+
.e($record->developerAccount->stripe_connect_account_id)
83+
.' &#8599;</a>'
84+
)),
85+
Forms\Components\Placeholder::make('developerAccount.country')
86+
->label('Country')
87+
->content(fn (User $record) => $record->developerAccount->country ?? ''),
88+
Forms\Components\Placeholder::make('developerAccount.payout_currency')
89+
->label('Payout Currency')
90+
->content(fn (User $record) => strtoupper($record->developerAccount->payout_currency ?? '')),
91+
Forms\Components\Placeholder::make('developerAccount.payouts_enabled')
92+
->label('Payouts Enabled')
93+
->content(fn (User $record) => $record->developerAccount->payouts_enabled ? 'Yes' : 'No'),
94+
Forms\Components\Placeholder::make('developerAccount.charges_enabled')
95+
->label('Charges Enabled')
96+
->content(fn (User $record) => $record->developerAccount->charges_enabled ? 'Yes' : 'No'),
97+
Forms\Components\Placeholder::make('developerAccount.onboarding_completed_at')
98+
->label('Onboarding Completed')
99+
->content(fn (User $record) => $record->developerAccount->onboarding_completed_at?->format('M j, Y g:i A') ?? ''),
100+
Forms\Components\Placeholder::make('developerAccount.accepted_plugin_terms_at')
101+
->label('Plugin Terms Accepted')
102+
->content(fn (User $record) => $record->developerAccount->accepted_plugin_terms_at?->format('M j, Y g:i A') ?? ''),
103+
Forms\Components\Placeholder::make('developerAccount.plugin_terms_version')
104+
->label('Terms Version')
105+
->content(fn (User $record) => $record->developerAccount->plugin_terms_version ?? ''),
106+
]),
65107
]);
66108
}
67109

@@ -85,6 +127,10 @@ public static function table(Table $table): Table
85127
Tables\Columns\TextColumn::make('anystack_contact_id')
86128
->hidden()
87129
->searchable(),
130+
Tables\Columns\IconColumn::make('developerAccount.id')
131+
->label('Developer')
132+
->boolean()
133+
->getStateUsing(fn (User $record) => $record->developerAccount !== null),
88134
Tables\Columns\TextColumn::make('created_at')
89135
->dateTime()
90136
->sortable(),
@@ -128,6 +174,7 @@ public static function table(Table $table): Table
128174
public static function getRelations(): array
129175
{
130176
return [
177+
RelationManagers\DeveloperPluginsRelationManager::class,
131178
RelationManagers\PluginLicensesRelationManager::class,
132179
RelationManagers\ProductLicensesRelationManager::class,
133180
RelationManagers\LicensesRelationManager::class,
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace App\Filament\Resources\UserResource\RelationManagers;
4+
5+
use App\Enums\PluginStatus;
6+
use App\Enums\PluginType;
7+
use App\Filament\Resources\PluginResource;
8+
use Filament\Resources\RelationManagers\RelationManager;
9+
use Filament\Tables;
10+
use Filament\Tables\Table;
11+
12+
class DeveloperPluginsRelationManager extends RelationManager
13+
{
14+
protected static string $relationship = 'plugins';
15+
16+
protected static ?string $title = 'Developer Plugins';
17+
18+
public function isReadOnly(): bool
19+
{
20+
return true;
21+
}
22+
23+
public function table(Table $table): Table
24+
{
25+
return $table
26+
->columns([
27+
Tables\Columns\TextColumn::make('name')
28+
->label('Package')
29+
->searchable()
30+
->sortable()
31+
->fontFamily('mono'),
32+
Tables\Columns\TextColumn::make('type')
33+
->badge()
34+
->color(fn (PluginType $state): string => match ($state) {
35+
PluginType::Free => 'gray',
36+
PluginType::Paid => 'success',
37+
}),
38+
Tables\Columns\TextColumn::make('status')
39+
->badge()
40+
->color(fn (PluginStatus $state): string => $state->color()),
41+
Tables\Columns\TextColumn::make('created_at')
42+
->label('Submitted')
43+
->dateTime()
44+
->sortable(),
45+
])
46+
->defaultSort('created_at', 'desc')
47+
->recordUrl(fn ($record) => PluginResource::getUrl('edit', ['record' => $record]));
48+
}
49+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
3+
namespace Tests\Feature\Filament;
4+
5+
use App\Filament\Resources\UserResource;
6+
use App\Filament\Resources\UserResource\Pages\EditUser;
7+
use App\Filament\Resources\UserResource\RelationManagers\DeveloperPluginsRelationManager;
8+
use App\Models\DeveloperAccount;
9+
use App\Models\Plugin;
10+
use App\Models\User;
11+
use Illuminate\Foundation\Testing\RefreshDatabase;
12+
use Livewire\Livewire;
13+
use Tests\TestCase;
14+
15+
class UserResourceDeveloperTest extends TestCase
16+
{
17+
use RefreshDatabase;
18+
19+
private User $admin;
20+
21+
private User $user;
22+
23+
protected function setUp(): void
24+
{
25+
parent::setUp();
26+
27+
$this->admin = User::factory()->create(['email' => 'admin@test.com']);
28+
config(['filament.users' => ['admin@test.com']]);
29+
30+
$this->user = User::factory()->create();
31+
}
32+
33+
public function test_developer_account_section_is_visible_when_user_has_developer_account(): void
34+
{
35+
DeveloperAccount::factory()->create([
36+
'user_id' => $this->user->id,
37+
]);
38+
39+
$this->actingAs($this->admin)
40+
->get(EditUser::getUrl(['record' => $this->user]))
41+
->assertSee('Developer Account')
42+
->assertSee($this->user->developerAccount->stripe_connect_account_id);
43+
}
44+
45+
public function test_developer_account_section_is_hidden_when_user_has_no_developer_account(): void
46+
{
47+
$this->actingAs($this->admin)
48+
->get(EditUser::getUrl(['record' => $this->user]))
49+
->assertDontSee('Developer Account');
50+
}
51+
52+
public function test_developer_account_section_shows_stripe_connect_link(): void
53+
{
54+
$developerAccount = DeveloperAccount::factory()->create([
55+
'user_id' => $this->user->id,
56+
]);
57+
58+
$this->actingAs($this->admin)
59+
->get(EditUser::getUrl(['record' => $this->user]))
60+
->assertSee('https://dashboard.stripe.com/connect/accounts/'.$developerAccount->stripe_connect_account_id);
61+
}
62+
63+
public function test_developer_plugins_relation_manager_lists_plugins(): void
64+
{
65+
$plugins = Plugin::factory()->count(3)->create([
66+
'user_id' => $this->user->id,
67+
]);
68+
69+
Livewire::actingAs($this->admin)
70+
->test(DeveloperPluginsRelationManager::class, [
71+
'ownerRecord' => $this->user,
72+
'pageClass' => EditUser::class,
73+
])
74+
->assertCanSeeTableRecords($plugins)
75+
->assertCountTableRecords(3);
76+
}
77+
78+
public function test_developer_plugins_relation_manager_does_not_show_other_users_plugins(): void
79+
{
80+
$otherUser = User::factory()->create();
81+
82+
Plugin::factory()->create(['user_id' => $this->user->id]);
83+
Plugin::factory()->create(['user_id' => $otherUser->id]);
84+
85+
Livewire::actingAs($this->admin)
86+
->test(DeveloperPluginsRelationManager::class, [
87+
'ownerRecord' => $this->user,
88+
'pageClass' => EditUser::class,
89+
])
90+
->assertCountTableRecords(1);
91+
}
92+
93+
public function test_developer_plugins_relation_manager_renders_successfully(): void
94+
{
95+
Livewire::actingAs($this->admin)
96+
->test(DeveloperPluginsRelationManager::class, [
97+
'ownerRecord' => $this->user,
98+
'pageClass' => EditUser::class,
99+
])
100+
->assertOk()
101+
->assertCountTableRecords(0);
102+
}
103+
104+
public function test_users_index_shows_developer_column(): void
105+
{
106+
DeveloperAccount::factory()->create([
107+
'user_id' => $this->user->id,
108+
]);
109+
110+
$nonDeveloper = User::factory()->create();
111+
112+
Livewire::actingAs($this->admin)
113+
->test(UserResource\Pages\ListUsers::class)
114+
->assertCanRenderTableColumn('developerAccount.id');
115+
}
116+
}

0 commit comments

Comments
 (0)