Skip to content

Commit af0eab3

Browse files
simonhampclaude
andcommitted
Add developer account details and plugins tab to admin user view
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 af0eab3

3 files changed

Lines changed: 212 additions & 0 deletions

File tree

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)