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
53 changes: 53 additions & 0 deletions app/HomeSlide.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class HomeSlide extends Model
{
protected $fillable = [
'title',
'description',
'url',
'button_text',
'url2',
'button2_text',
'image',
'position',
'active',
'show_countdown',
'countdown_target',
];

protected $casts = [
'active' => 'boolean',
'show_countdown' => 'boolean',
'countdown_target' => 'datetime',
'position' => 'integer',
];

public function scopeActive($query)
{
return $query->where('active', true);
}

public function scopeOrdered($query)
{
return $query->orderBy('position')->orderBy('id');
}

/**
* Full URL for image (prefix with base URL if stored as path).
*/
public function getImageUrlAttribute(): ?string
{
if (empty($this->image)) {
return null;
}
if (str_starts_with($this->image, 'http://') || str_starts_with($this->image, 'https://')) {
return $this->image;
}
return url(ltrim($this->image, '/'));
}
}
83 changes: 61 additions & 22 deletions app/Http/Controllers/HomeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,97 @@

namespace App\Http\Controllers;

use App\HomeSlide;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Schema;
use Illuminate\View\View;

class HomeController extends Controller
{
public function index(Request $request): View
{
$activities = collect([
/* [
'title' => 'home.banner1_title',
'description' => 'home.banner1_description',
'url' => '/dream-jobs-in-digital',
'style_color' => 'background-image: linear-gradient(36.92deg, #1C4DA1 20.32%, #0040AE 28.24%);',
'btn_lang' => 'home.get_involved',
], */
$activities = $this->getActivities();

return view('static.home', compact('activities'));
}

/**
* Homepage slider slides: from Nova (home_slides) when present, else default hardcoded set.
*/
private function getActivities()
{
if (Schema::hasTable('home_slides')) {
$slides = HomeSlide::active()->ordered()->get();
if ($slides->isNotEmpty()) {
return $slides->map(function (HomeSlide $slide) {
return [
'title' => $slide->title,
'description' => $slide->description ?? '',
'url' => $slide->url,
'btn_lang' => $slide->button_text,
'url2' => $slide->url2,
'btn2_lang' => $slide->button2_text,
'image' => $slide->image_url,
'show_countdown' => $slide->show_countdown,
'countdown_target' => $slide->countdown_target?->toIso8601String(),
];
})->values();
}
}

$defaultImages = [
asset('/images/dream-jobs/dream_jobs_bg.png'),
asset('/images/digital-girls/banner_bg.png'),
asset('images/homepage/slide1.png'),
asset('images/search/search_bg_lg_2.jpeg'),
asset('/images/homepage/festive_acts_of_digital_kindness.png'),
];

return collect([
[
'title' => 'home.banner5_title',
'description' => 'home.banner5_description',
'url' => '/future-ready-csr',
'style_color' => 'background: linear-gradient(36.92deg, rgb(51, 194, 233) 20.32%, rgb(0, 179, 227) 28.24%);',
'btn_lang' => 'home.learn_more',
'url2' => null,
'btn2_lang' => null
'btn2_lang' => null,
'image' => $defaultImages[0] ?? null,
'show_countdown' => false,
'countdown_target' => null,
],
[
[
'title' => 'home.banner4_title',
'description' => 'home.banner4_description',
'url' => 'https://codeweek.eu/blog/code-week-digital-educator-awards-2025/',
'style_color' => 'background-image: linear-gradient(36.92deg, #1C4DA1 20.32%, #0040AE 28.24%);',
'btn_lang' => 'home.view_winners',
'url2' => 'https://codeweek.eu/blog/code-week-digital-educator-awards-2025/ ',
// 'btn2_lang' => 'home.download_brochure_btn',
'url2' => 'https://codeweek.eu/blog/code-week-digital-educator-awards-2025/',
'btn2_lang' => null,
'image' => $defaultImages[1] ?? null,
'show_countdown' => false,
'countdown_target' => null,
],
[
'title' => 'home.banner6_title',
'description' => 'home.banner6_description',
'url' => 'https://airtable.com/appW5W6DJ6CI6SVdH/pagLDrU2NQja9F2vu/form',
'style_color' => 'background: linear-gradient(36.92deg, rgb(51, 194, 233) 20.32%, rgb(0, 179, 227) 28.24%);',
'btn_lang' => 'home.register_here',
'btn_lang' => 'home.register_here',
'url2' => null,
'btn2_lang' => null
'btn2_lang' => null,
'image' => $defaultImages[2] ?? null,
'show_countdown' => false,
'countdown_target' => null,
],
[
'title' => 'home.banner3_title',
'description' => __('home.when_text'),
'description' => 'home.when_text',
'url' => '/guide',
'style_color' => 'background-image: linear-gradient(36.92deg, #1C4DA1 20.32%, #0040AE 28.24%);',
'btn_lang' => 'home.get_involved',
'url2' => '/blog/code-week-25-programme/',
// 'btn2_lang' => 'home.download_brochure_btn',
]
'btn2_lang' => null,
'image' => $defaultImages[3] ?? null,
'show_countdown' => false,
'countdown_target' => null,
],
]);
return view('static.home', compact('activities'));
}
}
77 changes: 77 additions & 0 deletions app/Nova/HomeSlide.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

namespace App\Nova;

use Illuminate\Http\Request;
use Laravel\Nova\Fields\Boolean;
use Laravel\Nova\Fields\DateTime;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Number;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\Textarea;
use Laravel\Nova\Http\Requests\NovaRequest;

class HomeSlide extends Resource
{
public static $group = 'Content';

public static $model = \App\HomeSlide::class;

public static $title = 'title';

public static $search = ['title', 'description'];

public static function label()
{
return 'Homepage Slides';
}

public static function singularLabel()
{
return 'Homepage Slide';
}

public static function authorizedToViewAny(Request $request): bool
{
return true;
}

public function fields(Request $request): array
{
return [
ID::make()->sortable(),
Text::make('Title', 'title')
->rules('required')
->help('Lang key (e.g. home.banner1_title) or plain text.'),
Textarea::make('Description', 'description')
->nullable()
->help('Lang key or plain text.'),
Text::make('Primary button URL', 'url')->rules('required')->hideFromIndex(),
Text::make('Primary button label', 'button_text')
->rules('required')
->help('Lang key (e.g. home.learn_more) or plain text.'),
Text::make('Second button URL', 'url2')->nullable()->hideFromIndex(),
Text::make('Second button label', 'button2_text')
->nullable()
->help('Leave empty to hide second button.'),
Text::make('Image', 'image')
->nullable()
->help('Path e.g. /images/homepage/slide1.png or full URL. Used as slide background.'),
Number::make('Position', 'position')
->min(0)
->default(0)
->help('Lower = first. Order of slides on homepage.'),
Boolean::make('Active', 'active'),
Boolean::make('Show countdown', 'show_countdown')
->help('Show countdown timer on this slide (usually first slide).'),
DateTime::make('Countdown target', 'countdown_target')
->nullable()
->help('Target date/time for countdown (e.g. Code Week start). Only used if Show countdown is on.'),
];
}

public static function indexQuery(NovaRequest $request, $query)
{
return $query->orderBy('position')->orderBy('id');
}
}
38 changes: 38 additions & 0 deletions database/migrations/2026_02_09_140000_create_home_slides_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?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::create('home_slides', function (Blueprint $table) {
$table->id();
$table->string('title'); // Lang key (e.g. home.banner1_title) or plain text
$table->text('description')->nullable(); // Lang key or plain text
$table->string('url');
$table->string('button_text'); // Primary button label (lang key or text)
$table->string('url2')->nullable();
$table->string('button2_text')->nullable(); // Second button label (optional)
$table->string('image')->nullable(); // Path e.g. /images/homepage/slide1.png or full URL
$table->unsignedInteger('position')->default(0);
$table->boolean('active')->default(true);
$table->boolean('show_countdown')->default(false);
$table->dateTime('countdown_target')->nullable(); // e.g. 2025-10-14 00:00:00
$table->timestamps();
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('home_slides');
}
};
33 changes: 7 additions & 26 deletions resources/views/static/home.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,33 +35,25 @@ class="flex items-end duration-1000 home-activity codeweek-container-lg md:items
style="opacity: {{ $index === 0 ? 1 : 0 }}">
<div
class="px-6 py-10 max-md:w-full md:px-14 md:py-[4.5rem] bg-white rounded-[32px] z-10 relative">
@if ($index === 0)
{{-- <div x-data="countdownTimer('2025-10-14T00:00:00')" x-init="startCountdown()"
@if (!empty($activity['show_countdown']) && !empty($activity['countdown_target']))
<div x-data="countdownTimer('{{ $activity['countdown_target'] }}')" x-init="startCountdown()"
class="flex gap-2.5 items-start mt-4 mb-4 max-md:gap-2 max-sm:gap-1.5" role="timer"
aria-label="Countdown timer">

<!-- Days -->
<div class="px-2 py-1 text-3xl leading-9 text-white bg-black max-md:px-1.5 max-md:py-1 max-md:text-2xl max-md:leading-8 max-sm:px-1 max-sm:py-0.5 max-sm:text-xl max-sm:leading-7"
aria-label="Days" x-text="days">00</div>
<div class="text-3xl leading-9 text-black max-md:text-2xl max-md:leading-8 max-sm:text-xl max-sm:leading-7"
aria-hidden="true">:</div>

<!-- Hours -->
<div class="px-2 py-1 text-3xl leading-9 text-white bg-black max-md:px-1.5 max-md:py-1 max-md:text-2xl max-md:leading-8 max-sm:px-1 max-sm:py-0.5 max-sm:text-xl max-sm:leading-7"
aria-label="Hours" x-text="hours">00</div>
<div class="text-3xl leading-9 text-black max-md:text-2xl max-md:leading-8 max-sm:text-xl max-sm:leading-7"
aria-hidden="true">:</div>

<!-- Minutes -->
<div class="px-2 py-1 text-3xl leading-9 text-white bg-black max-md:px-1.5 max-md:py-1 max-md:text-2xl max-md:leading-8 max-sm:px-1 max-sm:py-0.5 max-sm:text-xl max-sm:leading-7"
aria-label="Minutes" x-text="minutes">00</div>
<div class="text-3xl leading-9 text-black max-md:text-2xl max-md:leading-8 max-sm:text-xl max-sm:leading-7"
aria-hidden="true">:</div>

<!-- Seconds -->
<div class="px-2 py-1 text-3xl leading-9 text-white bg-black max-md:px-1.5 max-md:py-1 max-md:text-2xl max-md:leading-8 max-sm:px-1 max-sm:py-0.5 max-sm:text-xl max-sm:leading-7"
aria-label="Seconds" x-text="seconds">00</div>
</div> --}}
</div>
@endif
<script>
function countdownTimer(targetDate) {
Expand Down Expand Up @@ -102,7 +94,7 @@ class="text-[#1C4DA1] text-[30px] md:text-[60px] leading-9 md:leading-[72px] fon
</h2>
<p
class="text-xl md:text-2xl leading-8 text-[#333E48] p-0 mb-4 max-md:max-w-full max-w-[525px]">
{{ strip_tags(__(''.$activity['description'])) }}
{{ strip_tags(__($activity['description'] ?? '')) }}
</p>
<div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0">
<a class="inline-block bg-primary hover:bg-hover-orange rounded-full py-4 px-6 md:px-10 font-semibold text-base w-full md:w-auto text-center text-[#20262C] transition-all duration-300"
Expand Down Expand Up @@ -130,26 +122,15 @@ class="absolute top-0 -translate-y-1/2 bg-yellow py-3 md:py-4 px-8 md:px-10 roun
</div>
</div>

@php
$backgroundImages = [
//asset('/images/csr/csr_about1.jpg'),
asset('/images/dream-jobs/dream_jobs_bg.png'),
asset('/images/digital-girls/banner_bg.png'),
asset('images/homepage/slide1.png'),
asset('images/search/search_bg_lg_2.jpeg'),
asset('/images/homepage/festive_acts_of_digital_kindness.png'),
];
@endphp

@if(isset($backgroundImages[$index]))
@if(!empty($activity['image']))
<img class="absolute top-0 -left-1/4 w-[150vw] !max-w-none md:hidden"
loading="lazy"
src="{{ $backgroundImages[$index] }}"
src="{{ $activity['image'] }}"
style="clip-path: ellipse(71% 73% at 40% 20%);" />

<img class="absolute top-0 right-0 h-full min-w-[55%] max-w-[calc(70vw)] object-cover hidden md:block"
loading="lazy"
src="{{ $backgroundImages[$index] }}"
src="{{ $activity['image'] }}"
style="clip-path: ellipse(70% 140% at 70% 25%);" />
@endif

Expand Down
Loading