Skip to content

Commit 8a3a7c1

Browse files
committed
Showcase!
1 parent b9cb05f commit 8a3a7c1

25 files changed

+1790
-48
lines changed
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
<?php
2+
3+
namespace App\Filament\Resources;
4+
5+
use App\Filament\Resources\ShowcaseResource\Pages;
6+
use App\Models\Showcase;
7+
use Filament\Forms;
8+
use Filament\Forms\Form;
9+
use Filament\Resources\Resource;
10+
use Filament\Tables;
11+
use Filament\Tables\Table;
12+
use Illuminate\Database\Eloquent\Builder;
13+
14+
class ShowcaseResource extends Resource
15+
{
16+
protected static ?string $model = Showcase::class;
17+
18+
protected static ?string $navigationIcon = 'heroicon-o-square-3-stack-3d';
19+
20+
protected static ?string $navigationLabel = 'Showcase';
21+
22+
protected static ?string $pluralModelLabel = 'Showcase Submissions';
23+
24+
public static function form(Form $form): Form
25+
{
26+
return $form
27+
->schema([
28+
Forms\Components\Section::make('App Details')
29+
->schema([
30+
Forms\Components\Select::make('user_id')
31+
->label('Submitted By')
32+
->relationship('user', 'email')
33+
->getOptionLabelFromRecordUsing(fn ($record) => $record->name ? "{$record->name} ({$record->email})" : $record->email)
34+
->searchable(['name', 'email'])
35+
->preload()
36+
->required(),
37+
38+
Forms\Components\TextInput::make('title')
39+
->required()
40+
->maxLength(255),
41+
42+
Forms\Components\Textarea::make('description')
43+
->required()
44+
->rows(4),
45+
46+
Forms\Components\FileUpload::make('image')
47+
->label('Main Image')
48+
->image()
49+
->disk('public')
50+
->directory('showcase-images'),
51+
52+
Forms\Components\FileUpload::make('screenshots')
53+
->label('Screenshots (up to 5)')
54+
->image()
55+
->multiple()
56+
->maxFiles(5)
57+
->disk('public')
58+
->directory('showcase-screenshots')
59+
->reorderable(),
60+
]),
61+
62+
Forms\Components\Section::make('Platform Availability')
63+
->schema([
64+
Forms\Components\Toggle::make('has_mobile')
65+
->label('Mobile App')
66+
->live(),
67+
68+
Forms\Components\Toggle::make('has_desktop')
69+
->label('Desktop App')
70+
->live(),
71+
72+
Forms\Components\Fieldset::make('Mobile Links')
73+
->visible(fn (Forms\Get $get) => $get('has_mobile'))
74+
->schema([
75+
Forms\Components\TextInput::make('app_store_url')
76+
->label('App Store URL')
77+
->url()
78+
->maxLength(255),
79+
80+
Forms\Components\TextInput::make('play_store_url')
81+
->label('Play Store URL')
82+
->url()
83+
->maxLength(255),
84+
]),
85+
86+
Forms\Components\Fieldset::make('Desktop Downloads')
87+
->visible(fn (Forms\Get $get) => $get('has_desktop'))
88+
->schema([
89+
Forms\Components\TextInput::make('windows_download_url')
90+
->label('Windows Download URL')
91+
->url()
92+
->maxLength(255),
93+
94+
Forms\Components\TextInput::make('macos_download_url')
95+
->label('macOS Download URL')
96+
->url()
97+
->maxLength(255),
98+
99+
Forms\Components\TextInput::make('linux_download_url')
100+
->label('Linux Download URL')
101+
->url()
102+
->maxLength(255),
103+
]),
104+
]),
105+
106+
Forms\Components\Section::make('Certification')
107+
->schema([
108+
Forms\Components\Toggle::make('certified_nativephp')
109+
->label('Certified as built with NativePHP')
110+
->disabled(),
111+
]),
112+
]);
113+
}
114+
115+
public static function table(Table $table): Table
116+
{
117+
return $table
118+
->columns([
119+
Tables\Columns\ImageColumn::make('image')
120+
->label('Image')
121+
->disk('public')
122+
->height(40)
123+
->toggleable(),
124+
125+
Tables\Columns\TextColumn::make('title')
126+
->searchable()
127+
->sortable(),
128+
129+
Tables\Columns\ViewColumn::make('platforms')
130+
->label('Platforms')
131+
->view('filament.tables.columns.platforms'),
132+
133+
Tables\Columns\TextColumn::make('approvedBy.name')
134+
->label('Approved By')
135+
->toggleable(),
136+
137+
Tables\Columns\TextColumn::make('created_at')
138+
->label('Submitted')
139+
->dateTime()
140+
->sortable()
141+
->toggleable(),
142+
143+
Tables\Columns\TextColumn::make('updated_at')
144+
->label('Updated')
145+
->dateTime()
146+
->sortable()
147+
->toggleable(isToggledHiddenByDefault: true),
148+
])
149+
->filters([
150+
Tables\Filters\TernaryFilter::make('approved_at')
151+
->label('Status')
152+
->placeholder('All submissions')
153+
->trueLabel('Approved')
154+
->falseLabel('Pending')
155+
->queries(
156+
true: fn (Builder $query) => $query->whereNotNull('approved_at'),
157+
false: fn (Builder $query) => $query->whereNull('approved_at'),
158+
),
159+
160+
Tables\Filters\TernaryFilter::make('has_mobile')
161+
->label('Mobile App')
162+
->placeholder('All')
163+
->trueLabel('Has Mobile')
164+
->falseLabel('No Mobile'),
165+
166+
Tables\Filters\TernaryFilter::make('has_desktop')
167+
->label('Desktop App')
168+
->placeholder('All')
169+
->trueLabel('Has Desktop')
170+
->falseLabel('No Desktop'),
171+
172+
Tables\Filters\Filter::make('needs_re_review')
173+
->label('Needs Re-Review')
174+
->query(fn (Builder $query): Builder => $query
175+
->whereNotNull('approved_at')
176+
->whereColumn('updated_at', '>', 'approved_at')),
177+
])
178+
->actions([
179+
Tables\Actions\Action::make('approve')
180+
->icon('heroicon-o-check')
181+
->color('success')
182+
->visible(fn (Showcase $record) => $record->isPending())
183+
->action(fn (Showcase $record) => $record->update([
184+
'approved_at' => now(),
185+
'approved_by' => auth()->id(),
186+
]))
187+
->requiresConfirmation()
188+
->modalHeading('Approve Submission')
189+
->modalDescription('Are you sure you want to approve this app for the Showcase?'),
190+
191+
Tables\Actions\Action::make('unapprove')
192+
->icon('heroicon-o-x-mark')
193+
->color('warning')
194+
->visible(fn (Showcase $record) => $record->isApproved())
195+
->action(fn (Showcase $record) => $record->update([
196+
'approved_at' => null,
197+
'approved_by' => null,
198+
]))
199+
->requiresConfirmation()
200+
->modalHeading('Unapprove Submission')
201+
->modalDescription('This will remove the app from the public Showcase.'),
202+
203+
Tables\Actions\EditAction::make(),
204+
Tables\Actions\DeleteAction::make(),
205+
])
206+
->bulkActions([
207+
Tables\Actions\BulkActionGroup::make([
208+
Tables\Actions\BulkAction::make('approve')
209+
->icon('heroicon-o-check')
210+
->color('success')
211+
->action(function ($records) {
212+
$records->each(fn (Showcase $record) => $record->update([
213+
'approved_at' => now(),
214+
'approved_by' => auth()->id(),
215+
]));
216+
})
217+
->requiresConfirmation()
218+
->modalHeading('Approve Selected Submissions')
219+
->modalDescription('Are you sure you want to approve all selected submissions?'),
220+
221+
Tables\Actions\DeleteBulkAction::make(),
222+
]),
223+
])
224+
->defaultSort('created_at', 'desc');
225+
}
226+
227+
public static function getRelations(): array
228+
{
229+
return [
230+
//
231+
];
232+
}
233+
234+
public static function getPages(): array
235+
{
236+
return [
237+
'index' => Pages\ListShowcases::route('/'),
238+
'create' => Pages\CreateShowcase::route('/create'),
239+
'edit' => Pages\EditShowcase::route('/{record}/edit'),
240+
];
241+
}
242+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace App\Filament\Resources\ShowcaseResource\Pages;
4+
5+
use App\Filament\Resources\ShowcaseResource;
6+
use Filament\Resources\Pages\CreateRecord;
7+
8+
class CreateShowcase extends CreateRecord
9+
{
10+
protected static string $resource = ShowcaseResource::class;
11+
12+
protected function mutateFormDataBeforeCreate(array $data): array
13+
{
14+
$data['certified_nativephp'] = true;
15+
$data['approved_at'] = now();
16+
$data['approved_by'] = auth()->id();
17+
18+
return $data;
19+
}
20+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace App\Filament\Resources\ShowcaseResource\Pages;
4+
5+
use App\Filament\Resources\ShowcaseResource;
6+
use Filament\Actions;
7+
use Filament\Resources\Pages\EditRecord;
8+
9+
class EditShowcase extends EditRecord
10+
{
11+
protected static string $resource = ShowcaseResource::class;
12+
13+
protected function getHeaderActions(): array
14+
{
15+
return [
16+
Actions\DeleteAction::make(),
17+
];
18+
}
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace App\Filament\Resources\ShowcaseResource\Pages;
4+
5+
use App\Filament\Resources\ShowcaseResource;
6+
use Filament\Actions;
7+
use Filament\Resources\Pages\ListRecords;
8+
9+
class ListShowcases extends ListRecords
10+
{
11+
protected static string $resource = ShowcaseResource::class;
12+
13+
protected function getHeaderActions(): array
14+
{
15+
return [
16+
Actions\CreateAction::make(),
17+
];
18+
}
19+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use App\Models\Showcase;
6+
use Illuminate\Contracts\View\View;
7+
use Illuminate\Http\Request;
8+
9+
class CustomerShowcaseController extends Controller
10+
{
11+
public function index(Request $request): View
12+
{
13+
$showcases = Showcase::where('user_id', $request->user()->id)
14+
->latest()
15+
->get();
16+
17+
return view('customer.showcase.index', compact('showcases'));
18+
}
19+
20+
public function create(): View
21+
{
22+
return view('customer.showcase.create');
23+
}
24+
25+
public function edit(Showcase $showcase): View
26+
{
27+
abort_if($showcase->user_id !== auth()->id(), 403);
28+
29+
return view('customer.showcase.edit', compact('showcase'));
30+
}
31+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use App\Models\Showcase;
6+
use Illuminate\Contracts\View\View;
7+
8+
class ShowcaseController extends Controller
9+
{
10+
public function index(?string $platform = null): View
11+
{
12+
$query = Showcase::approved()
13+
->latest('approved_at');
14+
15+
if ($platform === 'mobile') {
16+
$query->withMobile();
17+
} elseif ($platform === 'desktop') {
18+
$query->withDesktop();
19+
}
20+
21+
$showcases = $query->paginate(10);
22+
23+
return view('showcase', [
24+
'showcases' => $showcases,
25+
'platform' => $platform,
26+
]);
27+
}
28+
}

0 commit comments

Comments
 (0)