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
4 changes: 2 additions & 2 deletions app/Enums/PriceTier.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public function label(): string
{
return match ($this) {
self::Regular => 'Regular',
self::Subscriber => 'Pro/Max Subscriber',
self::Subscriber => 'Subscriber',
self::Eap => 'Early Access',
};
}
Expand All @@ -21,7 +21,7 @@ public function description(): string
{
return match ($this) {
self::Regular => 'Standard pricing for all customers',
self::Subscriber => 'Discounted pricing for Pro and Max license holders',
self::Subscriber => 'Discounted pricing for subscribers',
self::Eap => 'Special pricing for Early Access Program customers',
};
}
Expand Down
8 changes: 5 additions & 3 deletions app/Filament/Resources/SupportTicketResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ public static function infolist(Schema $schema): Schema
Infolists\Components\TextEntry::make('issue_type')
->label('Issue Type')
->placeholder('N/A'),
Infolists\Components\TextEntry::make('user.email')
Infolists\Components\TextEntry::make('user.name')
->label('User')
->formatStateUsing(fn (SupportTicket $record): string => ($record->user->name ?? '').' ('.$record->user->email.')')
->url(fn (SupportTicket $record): string => UserResource::getUrl('edit', ['record' => $record->user_id])),
Infolists\Components\TextEntry::make('created_at')
->label('Created')
Expand Down Expand Up @@ -96,9 +97,10 @@ public static function table(Table $table): Table
->sortable()
->limit(50),

Tables\Columns\TextColumn::make('user.email')
Tables\Columns\TextColumn::make('user.name')
->label('User')
->searchable()
->formatStateUsing(fn (SupportTicket $record): string => ($record->user->name ?? '').' ('.$record->user->email.')')
->searchable(query: fn ($query, string $search) => $query->whereHas('user', fn ($q) => $q->where('name', 'like', "%{$search}%")->orWhere('email', 'like', "%{$search}%")))
->sortable(),

Tables\Columns\TextColumn::make('product')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Filament\Actions;
use Filament\Forms\Components\Select;
use Filament\Resources\Pages\ViewRecord;
use STS\FilamentImpersonate\Actions\Impersonate;

class ViewSupportTicket extends ViewRecord
{
Expand All @@ -16,6 +17,8 @@ class ViewSupportTicket extends ViewRecord
protected function getHeaderActions(): array
{
return [
Impersonate::make()->impersonateRecord(fn () => $this->getRecord()->user),

Actions\Action::make('updateStatus')
->label('Update Status')
->icon('heroicon-o-arrow-path')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Filament\Resources\SupportTicketResource\Widgets;

use App\Models\SupportTicket\Reply;
use App\Notifications\SupportTicketReplied;
use Filament\Widgets\Widget;
use Illuminate\Database\Eloquent\Model;
Expand Down Expand Up @@ -42,4 +43,22 @@ public function sendReply(): void
$this->newMessage = '';
$this->isNote = false;
}

public function togglePin(int $replyId): void
{
$reply = Reply::where('support_ticket_id', $this->record->id)
->where('id', $replyId)
->where('note', true)
->firstOrFail();

if ($reply->pinned) {
$reply->update(['pinned' => false]);
} else {
Reply::where('support_ticket_id', $this->record->id)
->where('pinned', true)
->update(['pinned' => false]);

$reply->update(['pinned' => true]);
}
}
}
2 changes: 2 additions & 0 deletions app/Filament/Resources/UserResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Filament\Schemas\Schema;
use Filament\Tables;
use Filament\Tables\Table;
use STS\FilamentImpersonate\Actions\Impersonate;

class UserResource extends Resource
{
Expand Down Expand Up @@ -89,6 +90,7 @@ public static function table(Table $table): Table
//
])
->actions([
Impersonate::make(),
Actions\ActionGroup::make([
Actions\EditAction::make(),
Actions\Action::make('view_on_stripe')
Expand Down
3 changes: 3 additions & 0 deletions app/Filament/Resources/UserResource/Pages/EditUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Filament\Notifications\Notification;
use Filament\Resources\Pages\EditRecord;
use Illuminate\Support\Facades\Password;
use STS\FilamentImpersonate\Actions\Impersonate;

class EditUser extends EditRecord
{
Expand All @@ -21,6 +22,8 @@ class EditUser extends EditRecord
protected function getHeaderActions(): array
{
return [
Impersonate::make()->record($this->getRecord()),

Actions\ActionGroup::make([
Actions\Action::make('createStripeCustomer')
->label('Create Stripe Customer')
Expand Down
37 changes: 37 additions & 0 deletions app/Http/Controllers/UltraController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace App\Http\Controllers;

use App\Enums\PluginStatus;
use App\Enums\PluginType;
use App\Models\Plugin;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;

class UltraController extends Controller
{
public function index(): View|RedirectResponse
{
$user = Auth::user();

if (! $user->hasActiveUltraSubscription()) {
return to_route('pricing');
}

$subscription = $user->subscription();

$plugins = Plugin::query()
->where('is_official', true)
->where('is_active', true)
->where('status', PluginStatus::Approved)
->where('type', PluginType::Paid)
->orderBy('name')
->get();

return view('customer.ultra.index', [
'subscription' => $subscription,
'plugins' => $plugins,
]);
}
}
40 changes: 37 additions & 3 deletions app/Livewire/Customer/Support/Show.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\SupportTicket\Status;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\RateLimiter;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
Expand All @@ -33,10 +34,22 @@ public function reply(): void
{
$this->authorize('reply', $this->supportTicket);

$key = 'support-reply:'.auth()->id();

if (RateLimiter::tooManyAttempts($key, 10)) {
$seconds = RateLimiter::availableIn($key);

$this->addError('replyMessage', "You're sending messages too quickly. Please wait {$seconds} seconds.");

return;
}

$this->validate([
'replyMessage' => ['required', 'string', 'max:5000'],
]);

RateLimiter::hit($key, 60);

$reply = $this->supportTicket->replies()->create([
'user_id' => auth()->id(),
'message' => $this->replyMessage,
Expand All @@ -48,8 +61,6 @@ public function reply(): void

$this->replyMessage = '';
$this->supportTicket->load(['user', 'replies.user']);

session()->flash('success', 'Your reply has been sent.');
}

public function closeTicket(): void
Expand All @@ -60,7 +71,30 @@ public function closeTicket(): void
'status' => Status::CLOSED,
]);

session()->flash('success', __('account.support_ticket.close_ticket.success'));
$this->supportTicket->replies()->create([
'user_id' => null,
'message' => auth()->user()->name.' closed this ticket.',
'note' => false,
]);

$this->supportTicket->load(['user', 'replies.user']);
}

public function reopenTicket(): void
{
$this->authorize('reopenTicket', $this->supportTicket);

$this->supportTicket->update([
'status' => Status::OPEN,
]);

$this->supportTicket->replies()->create([
'user_id' => null,
'message' => auth()->user()->name.' reopened this ticket.',
'note' => false,
]);

$this->supportTicket->load(['user', 'replies.user']);
}

public function render(): View
Expand Down
17 changes: 7 additions & 10 deletions app/Models/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ public function routeParams(): array

protected static function booted(): void
{
static::saving(function (Plugin $plugin): void {
if ($plugin->isDirty('name') && $plugin->name && ! $plugin->isDirty('is_official')) {
$vendor = explode('/', $plugin->name)[0] ?? null;
$plugin->is_official = $vendor === 'nativephp';
}
});

static::created(function (Plugin $plugin): void {
$plugin->recordActivity(
PluginActivityType::Submitted,
Expand Down Expand Up @@ -185,16 +192,6 @@ public function getBestPriceForUser(?User $user): ?PluginPrice
->orderBy('amount', 'asc')
->first();

// Ultra subscribers get official plugins for free
if ($bestPrice && $user && $this->isOfficial() && $user->hasUltraAccess()) {
$freePrice = $bestPrice->replicate();
$freePrice->amount = 0;
$freePrice->id = $bestPrice->id;
$freePrice->exists = true;

return $freePrice;
}

return $bestPrice;
}

Expand Down
9 changes: 9 additions & 0 deletions app/Models/SupportTicket.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes;

class SupportTicket extends Model
Expand Down Expand Up @@ -54,6 +55,14 @@ public function replies(): HasMany
->orderBy('created_at', 'desc');
}

public function pinnedNote(): HasOne
{
return $this->hasOne(Reply::class)
->where('note', true)
->where('pinned', true)
->latestOfMany();
}

public function user(): BelongsTo
{
return $this->belongsTo(User::class);
Expand Down
2 changes: 2 additions & 0 deletions app/Models/SupportTicket/Reply.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ class Reply extends Model
'message',
'attachments',
'note',
'pinned',
];

protected $casts = [
'attachments' => 'array',
'note' => 'boolean',
'pinned' => 'boolean',
];

public function isFromAdmin(): Attribute
Expand Down
6 changes: 6 additions & 0 deletions app/Policies/SupportTicketPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ public function closeTicket(User $user, SupportTicket $supportTicket): bool
return $supportTicket->user_id === $user->id;
}

public function reopenTicket(User $user, SupportTicket $supportTicket): bool
{
return $supportTicket->user_id === $user->id
&& $supportTicket->status === Status::CLOSED;
}

/**
* Determine whether the user can view any models.
*/
Expand Down
5 changes: 5 additions & 0 deletions app/Providers/Filament/AdminPanelProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Support\Colors\Color;
use Filament\View\PanelsRenderHook;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
Expand All @@ -33,6 +34,10 @@ public function panel(Panel $panel): Panel
])
->brandName('NativePHP Admin')
->favicon(asset('favicon.ico'))
->renderHook(
PanelsRenderHook::HEAD_END,
fn (): string => '<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">',
)
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
->pages([
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"simonhamp/the-og": "^0.7.0",
"spatie/laravel-menu": "^4.1",
"spatie/yaml-front-matter": "^2.0",
"stechstudio/filament-impersonate": "^5.1",
"symfony/http-client": "^7.2",
"symfony/mailgun-mailer": "^7.1",
"torchlight/torchlight-commonmark": "^0.6.0"
Expand Down
Loading
Loading