Skip to content

Commit 0b9aacf

Browse files
authored
Merge branch 'main' into consulting-page
2 parents 913abe8 + 9ae13eb commit 0b9aacf

21 files changed

Lines changed: 1387 additions & 194 deletions

app/Filament/Resources/PluginResource.php

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,16 @@ public static function form(Schema $schema): Schema
5252
Forms\Components\Placeholder::make('logo_preview')
5353
->label('Logo')
5454
->content(fn (?Plugin $record) => $record?->hasLogo()
55-
? new HtmlString('<img src="'.e($record->getLogoUrl()).'" alt="Logo" class="w-16 h-16 rounded-lg object-cover" />')
55+
? new HtmlString('<img src="'.e($record->getLogoUrl()).'" alt="Logo" style="max-width: 256px; max-height: 256px; border-radius: 0.5rem; object-fit: cover;" />')
5656
: 'No logo')
5757
->visible(fn (?Plugin $record) => $record !== null),
5858

59-
Forms\Components\TextInput::make('name')
60-
->label('Composer Package Name'),
59+
Forms\Components\TextInput::make('display_name')
60+
->label('Display Name'),
61+
62+
Forms\Components\Placeholder::make('name')
63+
->label('Composer Package Name')
64+
->content(fn (?Plugin $record) => $record?->name ?? '-'),
6165

6266
Forms\Components\Select::make('type')
6367
->options(PluginType::class),
@@ -67,12 +71,29 @@ public static function form(Schema $schema): Schema
6771
->placeholder('No tier')
6872
->helperText('Set pricing tier for paid plugins'),
6973

70-
Forms\Components\TextInput::make('repository_url')
74+
Forms\Components\Placeholder::make('repository_url')
7175
->label('Repository URL')
76+
->content(fn (?Plugin $record) => $record?->repository_url
77+
? new HtmlString('<a href="'.e($record->repository_url).'" target="_blank" rel="noopener noreferrer" class="text-primary-600 hover:underline">'.e($record->repository_url).' ↗</a>')
78+
: '-'),
79+
80+
Forms\Components\Placeholder::make('license_type')
81+
->label('License')
82+
->content(function (?Plugin $record) {
83+
$license = $record?->getLicense();
84+
$licenseUrl = $record?->getLicenseUrl();
85+
86+
if (! $license) {
87+
return '-';
88+
}
7289

73-
->url()
74-
->suffixIcon('heroicon-o-arrow-top-right-on-square')
75-
->suffixIconColor('gray'),
90+
if ($licenseUrl) {
91+
return new HtmlString('<a href="'.e($licenseUrl).'" target="_blank" rel="noopener noreferrer" class="text-primary-600 hover:underline">'.e($license).' ↗</a>');
92+
}
93+
94+
return $license;
95+
})
96+
->visible(fn (?Plugin $record) => $record !== null),
7697

7798
Forms\Components\Select::make('status')
7899
->options(PluginStatus::class)
@@ -193,8 +214,9 @@ public static function form(Schema $schema): Schema
193214
->searchable()
194215
->preload(),
195216

196-
Forms\Components\DateTimePicker::make('created_at')
197-
->label('Submitted At'),
217+
Forms\Components\Placeholder::make('created_at')
218+
->label('Submitted At')
219+
->content(fn (?Plugin $record) => $record?->created_at?->format('M j, Y g:i A') ?? '-'),
198220

199221
Forms\Components\Select::make('approved_by')
200222
->relationship('approvedBy', 'email')

app/Filament/Resources/PluginResource/Pages/EditPlugin.php

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -24,34 +24,34 @@ class EditPlugin extends EditRecord
2424
protected function getHeaderActions(): array
2525
{
2626
return [
27-
Actions\ActionGroup::make([
28-
Actions\Action::make('approve')
29-
->icon('heroicon-o-check')
30-
->color('success')
31-
->visible(fn () => $this->record->isPending())
32-
->disabled(fn () => ! $this->record->passesRequiredReviewChecks())
33-
->action(fn () => $this->record->approve(auth()->id()))
34-
->requiresConfirmation()
35-
->modalHeading('Approve Plugin')
36-
->modalDescription(fn () => ! $this->record->passesRequiredReviewChecks()
37-
? "Cannot approve '{$this->record->name}' — required checks are failing: ".implode(', ', $this->record->getFailingRequiredChecks())
38-
: "Are you sure you want to approve '{$this->record->name}'?"),
27+
Actions\Action::make('approve')
28+
->icon('heroicon-o-check')
29+
->color('success')
30+
->visible(fn () => $this->record->isPending())
31+
->disabled(fn () => ! $this->record->passesRequiredReviewChecks())
32+
->action(fn () => $this->record->approve(auth()->id()))
33+
->requiresConfirmation()
34+
->modalHeading('Approve Plugin')
35+
->modalDescription(fn () => ! $this->record->passesRequiredReviewChecks()
36+
? "Cannot approve '{$this->record->name}' — required checks are failing: ".implode(', ', $this->record->getFailingRequiredChecks())
37+
: "Are you sure you want to approve '{$this->record->name}'?"),
3938

40-
Actions\Action::make('reject')
41-
->icon('heroicon-o-x-mark')
42-
->color('danger')
43-
->visible(fn () => $this->record->isPending() || $this->record->isApproved())
44-
->form([
45-
Forms\Components\Textarea::make('rejection_reason')
46-
->label('Reason for Rejection')
47-
->required()
48-
->rows(3)
49-
->placeholder('Please explain why this plugin is being rejected...'),
50-
])
51-
->action(fn (array $data) => $this->record->reject($data['rejection_reason'], auth()->id()))
52-
->modalHeading('Reject Plugin')
53-
->modalDescription(fn () => "Are you sure you want to reject '{$this->record->name}'?"),
39+
Actions\Action::make('reject')
40+
->icon('heroicon-o-x-mark')
41+
->color('danger')
42+
->visible(fn () => $this->record->isPending() || $this->record->isApproved())
43+
->form([
44+
Forms\Components\Textarea::make('rejection_reason')
45+
->label('Reason for Rejection')
46+
->required()
47+
->rows(3)
48+
->placeholder('Please explain why this plugin is being rejected...'),
49+
])
50+
->action(fn (array $data) => $this->record->reject($data['rejection_reason'], auth()->id()))
51+
->modalHeading('Reject Plugin')
52+
->modalDescription(fn () => "Are you sure you want to reject '{$this->record->name}'?"),
5453

54+
Actions\ActionGroup::make([
5555
Actions\Action::make('convertToPaid')
5656
->label('Convert to Paid')
5757
->icon('heroicon-o-currency-dollar')
@@ -106,6 +106,7 @@ protected function getHeaderActions(): array
106106
->label('Grant to User')
107107
->icon('heroicon-o-gift')
108108
->color('success')
109+
->visible(fn () => $this->record->isApproved())
109110
->form([
110111
Forms\Components\Select::make('user_id')
111112
->label('User')
@@ -158,14 +159,6 @@ protected function getHeaderActions(): array
158159
->modalDescription(fn () => "Grant '{$this->record->name}' to a user for free.")
159160
->modalSubmitActionLabel('Grant'),
160161

161-
Actions\Action::make('viewListing')
162-
->label('View Listing Page')
163-
->icon('heroicon-o-eye')
164-
->color('gray')
165-
->url(fn () => route('plugins.show', $this->record->routeParams()))
166-
->openUrlInNewTab()
167-
->visible(fn () => $this->record->isApproved() || $this->record->isPending()),
168-
169162
Actions\Action::make('viewPackagist')
170163
->label('View on Packagist')
171164
->icon('heroicon-o-arrow-top-right-on-square')
@@ -259,6 +252,14 @@ protected function getHeaderActions(): array
259252
->visible(fn () => $this->record->repository_url !== null)
260253
->url(fn () => $this->record->getGithubUrl())
261254
->openUrlInNewTab(),
255+
256+
Actions\Action::make('viewListing')
257+
->label('View Listing Page')
258+
->icon('heroicon-o-eye')
259+
->color('gray')
260+
->url(fn () => route('plugins.show', $this->record->routeParams()))
261+
->openUrlInNewTab()
262+
->visible(fn () => $this->record->isApproved() || $this->record->isPending()),
262263
])
263264
->icon('heroicon-m-ellipsis-vertical'),
264265
];

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+
}

app/Http/Controllers/PluginWebhookController.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ public function __invoke(Request $request, string $secret, PluginSyncService $sy
1818
return response()->json(['error' => 'Invalid webhook secret'], 404);
1919
}
2020

21+
$event = $request->header('X-GitHub-Event');
22+
23+
if ($event === 'ping') {
24+
return response()->json(['success' => true, 'message' => 'pong']);
25+
}
26+
2127
if (! $plugin->isApproved()) {
2228
return response()->json(['error' => 'Plugin is not approved'], 403);
2329
}
2430

25-
$event = $request->header('X-GitHub-Event');
26-
2731
if ($event === 'release') {
2832
// Sync plugin metadata to update latest_version
2933
$syncService->sync($plugin);

app/Jobs/ReviewPluginRepository.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,29 @@ public function handle(): array
2525
{
2626
$repo = $this->plugin->getRepositoryOwnerAndName();
2727

28+
$failedChecks = [
29+
'has_license_file' => false,
30+
'has_release_version' => false,
31+
'release_version' => null,
32+
'supports_ios' => false,
33+
'supports_android' => false,
34+
'supports_js' => false,
35+
'requires_mobile_sdk' => false,
36+
'mobile_sdk_constraint' => null,
37+
'has_ios_min_version' => false,
38+
'ios_min_version' => null,
39+
'has_android_min_version' => false,
40+
'android_min_version' => null,
41+
];
42+
2843
if (! $repo) {
2944
Log::warning('[ReviewPluginRepository] No valid repository URL', [
3045
'plugin_id' => $this->plugin->id,
3146
]);
3247

33-
return [];
48+
$this->plugin->update(['review_checks' => $failedChecks, 'reviewed_at' => now()]);
49+
50+
return $failedChecks;
3451
}
3552

3653
$token = $this->getGitHubToken();
@@ -44,7 +61,9 @@ public function handle(): array
4461
'plugin_id' => $this->plugin->id,
4562
]);
4663

47-
return [];
64+
$this->plugin->update(['review_checks' => $failedChecks, 'reviewed_at' => now()]);
65+
66+
return $failedChecks;
4867
}
4968

5069
$tree = $this->fetchRepoTree($owner, $repoName, $defaultBranch, $token);

0 commit comments

Comments
 (0)