Skip to content
Merged
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
191 changes: 191 additions & 0 deletions app/Filament/Resources/WallOfLoveSubmissionResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
<?php

namespace App\Filament\Resources;

use App\Filament\Resources\WallOfLoveSubmissionResource\Pages;
use App\Models\WallOfLoveSubmission;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;

class WallOfLoveSubmissionResource extends Resource
{
protected static ?string $model = WallOfLoveSubmission::class;

protected static ?string $navigationIcon = 'heroicon-o-heart';

protected static ?string $navigationLabel = 'Wall of Love';

protected static ?string $pluralModelLabel = 'Wall of Love Submissions';

public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Section::make('Submission Details')
->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),

Forms\Components\TextInput::make('company')
->maxLength(255),

Forms\Components\FileUpload::make('photo_path')
->label('Photo')
->image()
->disk('public')
->directory('wall-of-love-photos'),

Forms\Components\TextInput::make('url')
->label('Website/Social URL')
->url()
->maxLength(255),

Forms\Components\Textarea::make('testimonial')
->maxLength(1000)
->rows(4),
]),

Forms\Components\Section::make('Review Information')
->schema([
Forms\Components\Select::make('user_id')
->relationship('user', 'name')
->required()
->disabled(),

Forms\Components\DateTimePicker::make('approved_at')
->label('Approved At'),

Forms\Components\Select::make('approved_by')
->relationship('approvedBy', 'name')
->label('Approved By'),

Forms\Components\Placeholder::make('created_at')
->label('Submitted At')
->content(fn (WallOfLoveSubmission $record): ?string => $record->created_at?->diffForHumans()),
]),
]);
}

public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->searchable()
->sortable(),

Tables\Columns\TextColumn::make('company')
->searchable()
->toggleable(),

Tables\Columns\TextColumn::make('user.name')
->label('Submitted By')
->searchable()
->sortable(),

Tables\Columns\ImageColumn::make('photo_path')
->label('Photo')
->disk('public')
->height(40)
->toggleable(),

Tables\Columns\IconColumn::make('approved_at')
->label('Status')
->boolean()
->trueIcon('heroicon-o-check-circle')
->falseIcon('heroicon-o-clock')
->trueColor('success')
->falseColor('warning')
->sortable(),

Tables\Columns\TextColumn::make('approvedBy.name')
->label('Approved By')
->toggleable(),

Tables\Columns\TextColumn::make('created_at')
->label('Submitted')
->dateTime()
->sortable()
->toggleable(),
])
->filters([
Tables\Filters\TernaryFilter::make('approved_at')
->label('Status')
->placeholder('All submissions')
->trueLabel('Approved')
->falseLabel('Pending')
->queries(
true: fn (Builder $query) => $query->whereNotNull('approved_at'),
false: fn (Builder $query) => $query->whereNull('approved_at'),
),
])
->actions([
Tables\Actions\Action::make('approve')
->icon('heroicon-o-check')
->color('success')
->visible(fn (WallOfLoveSubmission $record) => $record->isPending())
->action(fn (WallOfLoveSubmission $record) => $record->update([
'approved_at' => now(),
'approved_by' => auth()->id(),
]))
->requiresConfirmation()
->modalHeading('Approve Submission')
->modalDescription('Are you sure you want to approve this submission for the Wall of Love?'),

Tables\Actions\Action::make('unapprove')
->icon('heroicon-o-x-mark')
->color('warning')
->visible(fn (WallOfLoveSubmission $record) => $record->isApproved())
->action(fn (WallOfLoveSubmission $record) => $record->update([
'approved_at' => null,
'approved_by' => null,
]))
->requiresConfirmation()
->modalHeading('Unapprove Submission')
->modalDescription('Are you sure you want to unapprove this submission?'),

Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\BulkAction::make('approve')
->icon('heroicon-o-check')
->color('success')
->action(function ($records) {
$records->each(fn (WallOfLoveSubmission $record) => $record->update([
'approved_at' => now(),
'approved_by' => auth()->id(),
]));
})
->requiresConfirmation()
->modalHeading('Approve Selected Submissions')
->modalDescription('Are you sure you want to approve all selected submissions?'),

Tables\Actions\DeleteBulkAction::make(),
]),
])
->defaultSort('created_at', 'desc');
}

public static function getRelations(): array
{
return [
//
];
}

public static function getPages(): array
{
return [
'index' => Pages\ListWallOfLoveSubmissions::route('/'),
// 'create' => Pages\CreateWallOfLoveSubmission::route('/create'),
'edit' => Pages\EditWallOfLoveSubmission::route('/{record}/edit'),
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace App\Filament\Resources\WallOfLoveSubmissionResource\Pages;

use App\Filament\Resources\WallOfLoveSubmissionResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;

class EditWallOfLoveSubmission extends EditRecord
{
protected static string $resource = WallOfLoveSubmissionResource::class;

protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace App\Filament\Resources\WallOfLoveSubmissionResource\Pages;

use App\Filament\Resources\WallOfLoveSubmissionResource;
use Filament\Resources\Pages\ListRecords;

class ListWallOfLoveSubmissions extends ListRecords
{
protected static string $resource = WallOfLoveSubmissionResource::class;

protected function getHeaderActions(): array
{
return [
];
}
}
28 changes: 28 additions & 0 deletions app/Http/Controllers/WallOfLoveSubmissionController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace App\Http\Controllers;

class WallOfLoveSubmissionController extends Controller
{
public function create()
{
// Check if user is eligible (has early adopter license)
$hasEarlyAdopterLicense = auth()->user()
->licenses()
->where('created_at', '<', '2025-06-01')
->exists();

if (! $hasEarlyAdopterLicense) {
abort(404);
}

// Check if user already has a submission
$hasExistingSubmission = auth()->user()->wallOfLoveSubmissions()->exists();

if ($hasExistingSubmission) {
return redirect()->route('customer.licenses')->with('info', 'You have already submitted your story to the Wall of Love.');
}

return view('customer.wall-of-love.create');
}
}
33 changes: 33 additions & 0 deletions app/Livewire/WallOfLoveBanner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace App\Livewire;

use Livewire\Component;

class WallOfLoveBanner extends Component
{
public function dismissBanner(): void
{
cache()->put('wall_of_love_dismissed_'.auth()->id(), true, now()->addWeek());
$this->dispatch('banner-dismissed');
}

public function shouldShowBanner(): bool
{
// Check if user has early adopter licenses (before June 1st, 2025)
$hasEarlyAdopterLicenses = auth()->user()->licenses()->where('created_at', '<', '2025-06-01')->exists();

// Check if user already submitted
$hasExistingSubmission = auth()->user()->wallOfLoveSubmissions()->exists();

// Check if banner was dismissed
$hasDismissedBanner = cache()->has('wall_of_love_dismissed_'.auth()->id());

return $hasEarlyAdopterLicenses && ! $hasExistingSubmission && ! $hasDismissedBanner;
}

public function render()
{
return view('livewire.wall-of-love-banner');
}
}
62 changes: 62 additions & 0 deletions app/Livewire/WallOfLoveSubmissionForm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace App\Livewire;

use App\Models\WallOfLoveSubmission;
use Livewire\Component;
use Livewire\Features\SupportFileUploads\WithFileUploads;

class WallOfLoveSubmissionForm extends Component
{
use WithFileUploads;

public $name = '';

public $company = '';

public $photo;

public $url = '';

public $testimonial = '';

protected $rules = [
'name' => 'required|string|max:255',
'company' => 'nullable|string|max:255',
'photo' => 'nullable|image|max:2048', // 2MB max
'url' => 'nullable|url|max:255',
'testimonial' => 'nullable|string|max:1000',
];

public function mount()
{
// Pre-fill name if user has a name
$this->name = auth()->user()->name ?? '';
}

public function submit()
{
$this->validate();

$photoPath = null;
if ($this->photo) {
$photoPath = $this->photo->store('wall-of-love-photos', 'public');
}

WallOfLoveSubmission::create([
'user_id' => auth()->id(),
'name' => $this->name,
'company' => $this->company ?: null,
'photo_path' => $photoPath,
'url' => $this->url ?: null,
'testimonial' => $this->testimonial ?: null,
]);

return redirect()->route('customer.licenses')->with('success', 'Thank you! Your submission has been received and is awaiting review.');
}

public function render()
{
return view('livewire.wall-of-love-submission-form');
}
}
2 changes: 1 addition & 1 deletion app/Models/License.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public function canCreateSubLicense(): bool

public function isLegacy(): bool
{
return !$this->subscription_item_id
return ! $this->subscription_item_id
&& $this->created_at->lt(Carbon::create(2025, 5, 8));
}

Expand Down
9 changes: 8 additions & 1 deletion app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
use Illuminate\Support\Collection;
use Laravel\Cashier\Billable;
use Laravel\Sanctum\HasApiTokens;
use Stripe\Customer;

class User extends Authenticatable implements FilamentUser
{
Expand Down Expand Up @@ -48,6 +47,14 @@ public function licenses(): HasMany
return $this->hasMany(License::class);
}

/**
* @return HasMany<WallOfLoveSubmission>
*/
public function wallOfLoveSubmissions(): HasMany
{
return $this->hasMany(WallOfLoveSubmission::class);
}

public function getFirstNameAttribute(): ?string
{
if (empty($this->name)) {
Expand Down
Loading