diff --git a/app/Console/Commands/DeleteUserAccount.php b/app/Console/Commands/DeleteUserAccount.php new file mode 100644 index 000000000..04c51a3ed --- /dev/null +++ b/app/Console/Commands/DeleteUserAccount.php @@ -0,0 +1,70 @@ +argument('email'))); + + /** @var User|null $user */ + $user = User::withTrashed() + ->whereRaw('LOWER(email) = ?', [$email]) + ->first(); + + if (!$user) { + $this->error("No user found for email: {$email}"); + return self::FAILURE; + } + + $this->info("Found user #{$user->id}: {$user->firstname} {$user->lastname} <{$user->email}>"); + + if ($this->option('dry-run')) { + $this->line('Dry run enabled. No changes were made.'); + return self::SUCCESS; + } + + if (!$this->option('force')) { + $confirmed = $this->confirm( + "This will permanently delete user #{$user->id} ({$user->email}). Continue?", + false + ); + + if (!$confirmed) { + $this->line('Cancelled.'); + return self::SUCCESS; + } + } + + try { + if ($this->option('queue')) { + ProcessUserDeletion::dispatch($user->id); + $this->info("Deletion job dispatched for user #{$user->id}."); + return self::SUCCESS; + } + + // Execute immediately for urgent/account-support requests. + (new ProcessUserDeletion($user->id))->handle(); + $this->info("User #{$user->id} has been deleted."); + + return self::SUCCESS; + } catch (Throwable $e) { + $this->error("Failed to delete user #{$user->id}: {$e->getMessage()}"); + return self::FAILURE; + } + } +} diff --git a/app/HackathonsPage.php b/app/HackathonsPage.php index 60f511146..34565fc05 100644 --- a/app/HackathonsPage.php +++ b/app/HackathonsPage.php @@ -12,6 +12,7 @@ class HackathonsPage extends Model 'dynamic_content', 'hero_title', 'hero_subtitle', + 'hero_background_image_url', 'intro_title', 'intro_paragraph_1', 'intro_paragraph_2', @@ -21,6 +22,7 @@ class HackathonsPage extends Model 'details_paragraph_3', 'details_paragraph_4', 'video_url', + 'video_poster_image_url', 'extra_button_text', 'extra_button_link', 'recap_button_text', diff --git a/app/Nova/HackathonsPage.php b/app/Nova/HackathonsPage.php index 1351c1bf0..d8e3578e2 100644 --- a/app/Nova/HackathonsPage.php +++ b/app/Nova/HackathonsPage.php @@ -49,6 +49,9 @@ public function fields(Request $request): array Panel::make('Hero', [ Text::make('Hero title', 'hero_title')->nullable(), Trix::make('Hero subtitle', 'hero_subtitle')->nullable(), + Text::make('Hero background image URL', 'hero_background_image_url') + ->nullable() + ->help('Optional. Use a relative path like /images/hackathons/hackathons_bg.png or a full URL.'), ])->collapsable()->collapsedByDefault(), Panel::make('Intro section', [ @@ -64,6 +67,9 @@ public function fields(Request $request): array Trix::make('Details paragraph 3', 'details_paragraph_3')->nullable(), Trix::make('Details paragraph 4', 'details_paragraph_4')->nullable(), Text::make('Video URL (embed)', 'video_url')->nullable(), + Text::make('Video poster image URL', 'video_poster_image_url') + ->nullable() + ->help('Optional. Image shown behind the play button. Use a relative path like /images/Visual%20for%20EU%20Finals%20webpage.png or a full URL.'), Text::make('Extra button text (optional)', 'extra_button_text')->nullable(), Text::make('Extra button link (optional)', 'extra_button_link')->nullable(), Text::make('Recap button text', 'recap_button_text')->nullable(), diff --git a/database/migrations/2026_04_07_120000_add_images_to_hackathons_page_table.php b/database/migrations/2026_04_07_120000_add_images_to_hackathons_page_table.php new file mode 100644 index 000000000..0c600fa1b --- /dev/null +++ b/database/migrations/2026_04_07_120000_add_images_to_hackathons_page_table.php @@ -0,0 +1,38 @@ +string('hero_background_image_url')->nullable()->after('hero_subtitle'); + }); + } + + if (Schema::hasTable('hackathons_page') && !Schema::hasColumn('hackathons_page', 'video_poster_image_url')) { + Schema::table('hackathons_page', function (Blueprint $table) { + $table->string('video_poster_image_url')->nullable()->after('video_url'); + }); + } + } + + public function down(): void + { + if (Schema::hasTable('hackathons_page') && Schema::hasColumn('hackathons_page', 'video_poster_image_url')) { + Schema::table('hackathons_page', function (Blueprint $table) { + $table->dropColumn('video_poster_image_url'); + }); + } + + if (Schema::hasTable('hackathons_page') && Schema::hasColumn('hackathons_page', 'hero_background_image_url')) { + Schema::table('hackathons_page', function (Blueprint $table) { + $table->dropColumn('hero_background_image_url'); + }); + } + } +}; + diff --git a/public/images/Visual for EU Finals webpage.png b/public/images/Visual for EU Finals webpage.png new file mode 100644 index 000000000..431d2b661 Binary files /dev/null and b/public/images/Visual for EU Finals webpage.png differ diff --git a/resources/views/hackathons/index.blade.php b/resources/views/hackathons/index.blade.php index d1e55fbb7..658389653 100644 --- a/resources/views/hackathons/index.blade.php +++ b/resources/views/hackathons/index.blade.php @@ -9,6 +9,13 @@ $hasHackathonsPageTable = \Illuminate\Support\Facades\Schema::hasTable('hackathons_page'); $page = $hasHackathonsPageTable ? \App\HackathonsPage::config() : null; $dynamic = $page && $page->dynamic_content; + + $defaultHeroBackground = '/images/hackathons/hackathons_bg.png'; + $heroBackground = ($dynamic && $page && $page->hero_background_image_url) ? $page->hero_background_image_url : $defaultHeroBackground; + + // Note: file on disk has spaces; URL must be encoded. + $defaultVideoPoster = '/images/Visual%20for%20EU%20Finals%20webpage.png'; + $videoPoster = ($dynamic && $page && $page->video_poster_image_url) ? $page->video_poster_image_url : $defaultVideoPoster; @endphp @section('layout.breadcrumb') @@ -46,13 +53,13 @@ @@ -105,7 +112,7 @@ class="animation-element move-background duration-[1.5s] absolute z-0 lg:-bottom
- +
@include('layout.video-player', [ 'id' => 'hackathons-video',