Added Support and Analytics for Release

This commit is contained in:
2026-07-10 21:59:35 +10:00
parent d765161b9a
commit 85eeba982f
24 changed files with 673 additions and 320 deletions
@@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers;
use App\Notifications\SupportRequestSubmitted;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Notification;
use Inertia\Inertia;
class SupportFormController extends Controller
{
public function view()
{
return Inertia::render('Contact/Support');
}
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'reason' => ['required', 'string', 'in:bug_report,general_feedback,missing_data'],
'message' => ['required', 'string', 'max:5000'],
]);
Notification::route('mail', config('mail.support_address', 'hello@flightsgoneby.com'))
->notify(new SupportRequestSubmitted(
name: $request->user()->name,
email: $request->user()->email,
reason: $validated['reason'],
message: $validated['message'],
));
return back()->with('success', 'Your message has been sent.');
}
}
+3
View File
@@ -2,12 +2,15 @@
namespace App\Models;
use App\Observers\AirlineObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
#[ObservedBy(AirlineObserver::class)]
class Airline extends Model
{
protected $table = 'airlines';
+3
View File
@@ -2,6 +2,8 @@
namespace App\Models;
use App\Observers\NotificationObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -26,6 +28,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
* @method static Builder active()
* @method static Builder forAchievements()
*/
#[ObservedBy(NotificationObserver::class)]
class Notification extends Model
{
protected $fillable = [
+5 -1
View File
@@ -2,10 +2,13 @@
namespace App\Models;
use App\Observers\UserObserver;
use App\Settings\SettingsRegistry;
use Database\Factories\UserFactory;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@@ -24,7 +27,8 @@ use App\Notifications\ResetPasswordNotification;
#[Fillable(['name', 'email', 'password', 'distance_unit', 'settings'])]
#[Hidden(['password', 'remember_token'])]
class User extends Authenticatable
#[ObservedBy(UserObserver::class)]
class User extends Authenticatable implements MustVerifyEmail
{
/** @use HasFactory<UserFactory> */
+11
View File
@@ -3,6 +3,8 @@
namespace App\Models;
use App\Http\Controllers\Api\AirlineApiController;
use App\Observers\FlightObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
@@ -10,6 +12,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
#[ObservedBy(FlightObserver::class)]
class UserFlight extends Model
{
protected $table = 'user_flights';
@@ -56,6 +59,7 @@ class UserFlight extends Model
'scope',
'range',
'region_range',
'class_seat_combined'
];
protected static function booted(): void
@@ -95,6 +99,13 @@ class UserFlight extends Model
);
}
protected function classSeatCombined(): Attribute
{
return Attribute::make(
get: fn() => $this->flightClass?->name
);
}
protected function departureTimeDisplay(): Attribute
{
return Attribute::make(
@@ -0,0 +1,47 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class SupportRequestSubmitted extends Notification implements ShouldQueue
{
use Queueable;
public function __construct(
public string $name,
public string $email,
public string $reason,
public string $message,
) {}
public function via($notifiable): array
{
return ['mail'];
}
public function toMail($notifiable): MailMessage
{
return (new MailMessage)
->theme('flightsgoneby')
->subject('New Support Request: ' . $this->reasonLabel())
->replyTo($this->email, $this->name)
->greeting('New Support Request')
->line('Reason: ' . $this->reasonLabel())
->line('From: ' . $this->name . ' (' . $this->email . ')')
->line($this->message);
}
protected function reasonLabel(): string
{
return match ($this->reason) {
'bug_report' => 'Bug Report',
'general_feedback' => 'General Feedback',
'missing_data' => 'Missing Data',
default => $this->reason,
};
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Observers;
use App\Models\Notification;
use App\Models\User;
class UserObserver
{
public function created(User $user): void
{
User::role('admin')
->where('id', '!=', $user->id)
->get(['id'])
->each(fn (User $admin) => Notification::create([
'user_id' => $admin->id,
'title' => 'New user registered',
'body' => "{$user->name} just signed up.",
'url' => null,
'is_achievement' => false,
'achievement_id' => null,
]));
}
}
-3
View File
@@ -34,9 +34,6 @@ class AppServiceProvider extends ServiceProvider
Mail::alwaysTo(config('app.test_email'));
}
Vite::prefetch(concurrency: 3);
UserFlight::observe(FlightObserver::class);
Airline::observe(AirlineObserver::class);
Notification::observe(NotificationObserver::class);
RateLimiter::for('api', function (Request $request) {
return $request->user('sanctum')
? Limit::perMinute(60)->by($request->user('sanctum')->id)
+13 -2
View File
@@ -110,7 +110,10 @@ class SettingsRegistry
'label' => 'Which columns to show on the Departure Board',
'default' => ['airline', 'flight_number', 'departure_airport', 'arrival_airport', 'departure_date', 'departure_time', 'arrival_time', 'duration', 'distance', 'aircraft', 'registration', 'class_seat_combined'],
'options' => [
['value' => 'airline', 'label' => 'Airline'],
['value' => 'airline', 'label' => 'Airline Logo w/ Info Popup'],
['value' => 'airline.name', 'label' => 'Airline Name'],
['value' => 'airline.iata_code', 'label' => 'Airline IATA Code'],
['value' => 'airline.icao_code', 'label' => 'Airline ICAO Code'],
['value' => 'flight_number', 'label' => 'Flight Number'],
['value' => 'departure_airport', 'label' => 'From'],
['value' => 'arrival_airport', 'label' => 'To'],
@@ -119,8 +122,16 @@ class SettingsRegistry
['value' => 'arrival_time', 'label' => 'Arrival Time'],
['value' => 'duration', 'label' => 'Duration'],
['value' => 'distance', 'label' => 'Distance'],
['value' => 'aircraft', 'label' => 'Aircraft'],
['value' => 'aircraft', 'label' => 'Aircraft Designator w/ Info Popup'],
['value' => 'registration', 'label' => 'Aircraft Registration'],
['value' => 'aircraft.manufacturer_code', 'label' => 'Aircraft Manufacturer'],
['value' => 'aircraft.model_full_name', 'label' => 'Aircraft Model'],
['value' => 'flight_class', 'label' => 'Class'],
['value' => 'seat_type_id', 'label' => 'Seat Type'],
['value' => 'seat_number', 'label' => 'Seat Number'],
['value' => 'flight_reason_id', 'label' => 'Reason'],
['value' => 'aircraft.display_name_short', 'label' => 'Aircraft Full Name'],
['value' => 'class_seat_combined', 'label' => 'Class/Seat Type/Seat Number Combined'],
],
],