Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/Filament/Resources/ShowcaseResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,28 @@ public static function form(Schema $schema): Schema
->url()
->maxLength(255),
]),

Schemas\Components\Fieldset::make('Platform-specific Screenshots')
->visible(fn (Schemas\Components\Utilities\Get $get) => $get('has_mobile') && $get('has_desktop'))
->schema([
Forms\Components\FileUpload::make('mobile_screenshots')
->label('Mobile Screenshots (optional, up to 5)')
->image()
->multiple()
->maxFiles(5)
->disk('public')
->directory('showcase-screenshots')
->reorderable(),

Forms\Components\FileUpload::make('desktop_screenshots')
->label('Desktop Screenshots (optional, up to 5)')
->image()
->multiple()
->maxFiles(5)
->disk('public')
->directory('showcase-screenshots')
->reorderable(),
]),
]),

Schemas\Components\Section::make('Certification')
Expand Down
2 changes: 2 additions & 0 deletions app/Http/Controllers/ShowcaseController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public function index(?string $platform = null): View
$query->withMobile();
} elseif ($platform === 'desktop') {
$query->withDesktop();
} elseif ($platform === 'both') {
$query->withMobile()->withDesktop();
}

$showcases = $query->paginate(10);
Expand Down
67 changes: 58 additions & 9 deletions app/Livewire/ShowcaseSubmissionForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ class ShowcaseSubmissionForm extends Component

public array $existingScreenshots = [];

#[Validate('nullable|array|max:5')]
public array $mobileScreenshots = [];

public array $existingMobileScreenshots = [];

#[Validate('nullable|array|max:5')]
public array $desktopScreenshots = [];

public array $existingDesktopScreenshots = [];

#[Validate('boolean')]
public bool $hasMobile = false;

Expand Down Expand Up @@ -67,6 +77,8 @@ public function mount(?Showcase $showcase = null): void
$this->description = $showcase->description;
$this->existingImage = $showcase->image;
$this->existingScreenshots = $showcase->screenshots ?? [];
$this->existingMobileScreenshots = $showcase->mobile_screenshots ?? [];
$this->existingDesktopScreenshots = $showcase->desktop_screenshots ?? [];
$this->hasMobile = $showcase->has_mobile;
$this->hasDesktop = $showcase->has_desktop;
$this->playStoreUrl = $showcase->play_store_url ?? '';
Expand All @@ -85,6 +97,8 @@ public function rules(): array
'description' => 'required|string|max:2000',
'image' => 'nullable|image|max:2048',
'screenshots.*' => 'nullable|image|max:2048',
'mobileScreenshots.*' => 'nullable|image|max:2048',
'desktopScreenshots.*' => 'nullable|image|max:2048',
'hasMobile' => 'boolean',
'hasDesktop' => 'boolean',
'playStoreUrl' => 'nullable|url|max:255',
Expand Down Expand Up @@ -115,11 +129,46 @@ public function removeExistingScreenshot(int $index): void
}
}

public function removeExistingMobileScreenshot(int $index): void
{
if (isset($this->existingMobileScreenshots[$index])) {
unset($this->existingMobileScreenshots[$index]);
$this->existingMobileScreenshots = array_values($this->existingMobileScreenshots);
}
}

public function removeExistingDesktopScreenshot(int $index): void
{
if (isset($this->existingDesktopScreenshots[$index])) {
unset($this->existingDesktopScreenshots[$index]);
$this->existingDesktopScreenshots = array_values($this->existingDesktopScreenshots);
}
}

public function removeExistingImage(): void
{
$this->existingImage = null;
}

/**
* @param array<int, string> $existing
* @param array<int, \Livewire\Features\SupportFileUploads\TemporaryUploadedFile> $uploads
* @return array<int, string>
*/
protected function storeScreenshots(array $existing, array $uploads): array
{
$paths = $existing;

foreach ($uploads as $upload) {
if (count($paths) >= 5) {
break;
}
$paths[] = $upload->store('showcase-screenshots', 'public');
}

return $paths;
}

public function submit(): mixed
{
$this->validate();
Expand All @@ -135,19 +184,19 @@ public function submit(): mixed
$imagePath = $this->image->store('showcase-images', 'public');
}

$screenshotPaths = $this->existingScreenshots;
foreach ($this->screenshots as $screenshot) {
if (count($screenshotPaths) >= 5) {
break;
}
$screenshotPaths[] = $screenshot->store('showcase-screenshots', 'public');
}
$bothPlatforms = $this->hasMobile && $this->hasDesktop;

$screenshotPaths = $this->storeScreenshots($this->existingScreenshots, $this->screenshots);
$mobileScreenshotPaths = $bothPlatforms ? $this->storeScreenshots($this->existingMobileScreenshots, $this->mobileScreenshots) : [];
$desktopScreenshotPaths = $bothPlatforms ? $this->storeScreenshots($this->existingDesktopScreenshots, $this->desktopScreenshots) : [];

$data = [
'title' => $this->title,
'description' => $this->description,
'image' => $imagePath,
'screenshots' => $screenshotPaths ?: null,
'mobile_screenshots' => $mobileScreenshotPaths ?: null,
'desktop_screenshots' => $desktopScreenshotPaths ?: null,
'has_mobile' => $this->hasMobile,
'has_desktop' => $this->hasDesktop,
'play_store_url' => $this->hasMobile ? ($this->playStoreUrl ?: null) : null,
Expand Down Expand Up @@ -199,8 +248,8 @@ public function delete(): mixed
Storage::disk('public')->delete($this->showcase->image);
}

if ($this->showcase->screenshots) {
foreach ($this->showcase->screenshots as $screenshot) {
foreach ([$this->showcase->screenshots, $this->showcase->mobile_screenshots, $this->showcase->desktop_screenshots] as $screenshots) {
foreach ($screenshots ?? [] as $screenshot) {
Storage::disk('public')->delete($screenshot);
}
}
Expand Down
18 changes: 18 additions & 0 deletions app/Models/Showcase.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class Showcase extends Model
'description',
'image',
'screenshots',
'mobile_screenshots',
'desktop_screenshots',
'has_mobile',
'has_desktop',
'play_store_url',
Expand Down Expand Up @@ -60,6 +62,20 @@ public function needsReReview(): bool
return $this->approved_at !== null && $this->updated_at->isAfter($this->approved_at);
}

/**
* @return array<int, string>
*/
public function screenshotsFor(?string $platform): array
{
$override = match ($platform) {
'mobile' => $this->mobile_screenshots,
'desktop' => $this->desktop_screenshots,
default => null,
};

return ! empty($override) ? $override : ($this->screenshots ?? []);
}

#[Scope]
protected function approved(Builder $query): Builder
{
Expand Down Expand Up @@ -88,6 +104,8 @@ protected function casts(): array
{
return [
'screenshots' => 'array',
'mobile_screenshots' => 'array',
'desktop_screenshots' => 'array',
'has_mobile' => 'boolean',
'has_desktop' => 'boolean',
'certified_nativephp' => 'boolean',
Expand Down
16 changes: 16 additions & 0 deletions database/factories/ShowcaseFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public function definition(): array
'description' => fake()->paragraph(3),
'image' => null,
'screenshots' => null,
'mobile_screenshots' => null,
'desktop_screenshots' => null,
'has_mobile' => $hasMobile,
'has_desktop' => $hasDesktop,
'play_store_url' => $hasMobile ? fake()->optional(0.7)->url() : null,
Expand Down Expand Up @@ -132,6 +134,20 @@ public function withWideScreenshots(int $count = 3): static
return $this->withScreenshots($count, tall: false);
}

public function withMobileScreenshots(int $count = 3): static
{
return $this->state(fn (array $attributes) => [
'mobile_screenshots' => array_map(fn () => $this->generatePlaceholderScreenshot(tall: true), range(1, $count)),
]);
}

public function withDesktopScreenshots(int $count = 3): static
{
return $this->state(fn (array $attributes) => [
'desktop_screenshots' => array_map(fn () => $this->generatePlaceholderScreenshot(tall: false), range(1, $count)),
]);
}

protected function generatePlaceholderScreenshot(bool $tall = false): string
{
$width = $tall ? 390 : 1280;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('showcases', function (Blueprint $table) {
$table->json('mobile_screenshots')->nullable()->after('screenshots');
$table->json('desktop_screenshots')->nullable()->after('mobile_screenshots');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('showcases', function (Blueprint $table) {
$table->dropColumn(['mobile_screenshots', 'desktop_screenshots']);
});
}
};
1 change: 1 addition & 0 deletions database/seeders/ShowcaseSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public function run(): void
Showcase::factory(2)->approved()->desktop()->withWideScreenshots(3)->create();
Showcase::factory(2)->approved()->desktop()->create();
Showcase::factory(2)->approved()->both()->withTallScreenshots(2)->create();
Showcase::factory(1)->approved()->both()->withMobileScreenshots(2)->withDesktopScreenshots(2)->create();

// Recently approved (will show as "new") with screenshots
Showcase::factory(2)->recentlyApproved()->mobile()->withTallScreenshots(4)->create();
Expand Down
14 changes: 9 additions & 5 deletions resources/views/components/showcase-card.blade.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
@props(['showcase'])
@props(['showcase', 'platform' => null])

@php
$displayScreenshots = $showcase->screenshotsFor($platform);
@endphp

<article
x-data="{
currentSlide: 0,
lightboxSlide: 0,
lightboxOpen: false,
screenshots: {{ json_encode($showcase->screenshots ?? []) }},
screenshotUrls: {{ json_encode(collect($showcase->screenshots ?? [])->map(fn($s) => Storage::disk('public')->url($s))->values()) }},
screenshots: {{ json_encode($displayScreenshots) }},
screenshotUrls: {{ json_encode(collect($displayScreenshots)->map(fn($s) => Storage::disk('public')->url($s))->values()) }},
get hasScreenshots() {
return this.screenshots && this.screenshots.length > 0
},
Expand Down Expand Up @@ -60,9 +64,9 @@ class="group relative overflow-hidden rounded-2xl border border-gray-200 bg-whit

{{-- Screenshot Carousel --}}
<div class="relative aspect-video bg-gray-100 dark:bg-gray-900 overflow-hidden">
@if($showcase->screenshots && count($showcase->screenshots) > 0)
@if(count($displayScreenshots) > 0)
<div class="relative h-full">
@foreach($showcase->screenshots as $index => $screenshot)
@foreach($displayScreenshots as $index => $screenshot)
<button
type="button"
x-show="currentSlide === {{ $index }}"
Expand Down
50 changes: 50 additions & 0 deletions resources/views/livewire/showcase-submission-form.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
{{-- Screenshots Field --}}
<flux:field>
<flux:label>Screenshots (up to 5)</flux:label>
@if ($hasMobile && $hasDesktop)
<flux:description>Shown on every platform. Add Mobile or Desktop screenshots below to override these for a specific platform.</flux:description>
@endif
@if (count($existingScreenshots) > 0)
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-5">
@foreach ($existingScreenshots as $index => $screenshot)
Expand Down Expand Up @@ -112,6 +115,53 @@
</flux:field>
</div>
@endif

{{-- Platform-specific Screenshots (shown when both platforms are selected) --}}
@if ($hasMobile && $hasDesktop)
<flux:field>
<flux:label>Mobile Screenshots (optional, up to 5)</flux:label>
@if (count($existingMobileScreenshots) > 0)
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-5">
@foreach ($existingMobileScreenshots as $index => $screenshot)
<div wire:key="mobile-screenshot-{{ $index }}" class="group relative">
<img src="{{ Storage::disk('public')->url($screenshot) }}" alt="Mobile screenshot {{ $index + 1 }}" class="h-32 w-full rounded-lg object-cover">
<button type="button" wire:click="removeExistingMobileScreenshot({{ $index }})" class="absolute right-1 top-1 rounded-full bg-red-600 p-1 text-white opacity-0 transition-opacity group-hover:opacity-100">
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
@endforeach
</div>
@endif
@if (count($existingMobileScreenshots) < 5)
<flux:input type="file" wire:model="mobileScreenshots" accept="image/*" multiple />
@endif
@error('mobileScreenshots.*') <flux:text class="text-sm text-red-600 dark:text-red-400">{{ $message }}</flux:text> @enderror
</flux:field>

<flux:field>
<flux:label>Desktop Screenshots (optional, up to 5)</flux:label>
@if (count($existingDesktopScreenshots) > 0)
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-5">
@foreach ($existingDesktopScreenshots as $index => $screenshot)
<div wire:key="desktop-screenshot-{{ $index }}" class="group relative">
<img src="{{ Storage::disk('public')->url($screenshot) }}" alt="Desktop screenshot {{ $index + 1 }}" class="h-32 w-full rounded-lg object-cover">
<button type="button" wire:click="removeExistingDesktopScreenshot({{ $index }})" class="absolute right-1 top-1 rounded-full bg-red-600 p-1 text-white opacity-0 transition-opacity group-hover:opacity-100">
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
@endforeach
</div>
@endif
@if (count($existingDesktopScreenshots) < 5)
<flux:input type="file" wire:model="desktopScreenshots" accept="image/*" multiple />
@endif
@error('desktopScreenshots.*') <flux:text class="text-sm text-red-600 dark:text-red-400">{{ $message }}</flux:text> @enderror
</flux:field>
@endif
</div>

<flux:error name="hasMobile" />
Expand Down
Loading