V2, better design, more functionalities.

This commit is contained in:
Roberto Guagliardo
2025-07-09 01:22:29 +02:00
parent b324c030f4
commit 2c5d7102ab
87 changed files with 2273 additions and 178 deletions

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
class ContactController extends Controller
{
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|max:255',
'message' => 'required|max:5000',
]);
return response()->json(['status' => 'success']);
}
}

View File

@@ -32,7 +32,7 @@ class EducationController extends Controller
$education = Education::create($data); $education = Education::create($data);
if ($request->hasFile('afbeelding')) { if ($request->hasFile('afbeelding')) {
$education->addMediaFromRequest('afbeelding')->toMediaCollection('afbeelding'); $education->addMediaFromRequest('afbeelding')->toMediaCollection('image');
} }
return redirect()->route('educations.index')->with('success', 'Opleiding toegevoegd.'); return redirect()->route('educations.index')->with('success', 'Opleiding toegevoegd.');
@@ -62,8 +62,9 @@ class EducationController extends Controller
$education->update($data); $education->update($data);
if ($request->hasFile('afbeelding')) { if ($request->hasFile('afbeelding')) {
$education->clearMediaCollection('afbeelding'); $education->clearMediaCollection('image');
$education->addMediaFromRequest('afbeelding')->toMediaCollection('afbeelding');
$education->addMediaFromRequest('afbeelding')->toMediaCollection('image');
} }
return redirect()->route('educations.index')->with('success', 'Opleiding bijgewerkt.'); return redirect()->route('educations.index')->with('success', 'Opleiding bijgewerkt.');

View File

@@ -7,15 +7,55 @@ use App\Models\Personalia;
use App\Models\Skill; use App\Models\Skill;
use App\Models\WorkExperience; use App\Models\WorkExperience;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Jobs\NotifyTelegramAboutPersonaliaClick;
use App\Jobs\NotifyTelegramAboutContactMessage;
class FrontendController extends Controller class FrontendController extends Controller
{ {
public function index() public function index()
{ {
$skills = Skill::all(); $skills = Skill::all()->groupBy('type');
$personalia = Personalia::all(); $personalia = Personalia::all();
$education = Education::all(); $education = Education::orderBy('startdatum', 'desc')->get();
$experience = WorkExperience::all(); $experience = WorkExperience::orderBy('startdatum', 'desc')->get();
return view('welcome', compact('skills', 'personalia', 'education', 'experience')); return view('welcome', compact('skills', 'personalia', 'education', 'experience'));
} }
public function getPersonalia($id)
{
$item = Personalia::findOrFail($id);
NotifyTelegramAboutPersonaliaClick::dispatch(
$item,
request()->ip(),
request()->userAgent()
);
return response()->json([
'value' => $item->value,
]);
}
public function message(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'message' => 'required|string|max:5000',
'email' => 'nullable|email|max:255',
'phone' => 'nullable|string|max:50',
]);
NotifyTelegramAboutContactMessage::dispatch(
$validated['name'],
$validated['message'],
$request->ip(),
$request->userAgent(),
$validated['email'] ?? null,
$validated['phone'] ?? null
);
return response()->json(['status' => 'success']);
}
} }

View File

@@ -29,11 +29,14 @@ class SkillController extends Controller
*/ */
public function store(Request $request) public function store(Request $request)
{ {
$validated = $request->validate([ $validated = $request->validate([
'title' => 'required|string|max:255', 'title' => 'required|string|max:255',
'description' => 'nullable|string', 'description' => 'nullable|string',
'rating' => 'required|integer|min:1|max:10', 'rating' => 'required|numeric|min:1|max:10',
'image' => 'nullable|image|max:2048', 'image' => 'nullable|image|max:2048',
'type' => 'required|in:rating,tag,other',
]); ]);
$skill = Skill::create($validated); $skill = Skill::create($validated);
@@ -69,8 +72,10 @@ class SkillController extends Controller
$validated = $request->validate([ $validated = $request->validate([
'title' => 'required|string|max:255', 'title' => 'required|string|max:255',
'description' => 'nullable|string', 'description' => 'nullable|string',
'rating' => 'required|integer|min:1|max:10', 'rating' => 'required|numeric|min:1|max:10',
'image' => 'nullable|image|max:2048', 'image' => 'nullable|image|max:2048',
'type' => 'required|in:rating,tag,other',
]); ]);
$skill->update($validated); $skill->update($validated);

View File

@@ -31,7 +31,7 @@ class WorkExperienceController extends Controller
$experience = WorkExperience::create($data); $experience = WorkExperience::create($data);
if ($request->hasFile('afbeelding')) { if ($request->hasFile('afbeelding')) {
$experience->addMediaFromRequest('afbeelding')->toMediaCollection('afbeelding'); $experience->addMediaFromRequest('afbeelding')->toMediaCollection('image');
} }
return redirect()->route('work-experiences.index')->with('success', 'Ervaring toegevoegd.'); return redirect()->route('work-experiences.index')->with('success', 'Ervaring toegevoegd.');
@@ -58,11 +58,13 @@ class WorkExperienceController extends Controller
'afbeelding' => 'nullable|image|max:2048', 'afbeelding' => 'nullable|image|max:2048',
]); ]);
$workExperience->update($data); $workExperience->update($data);
if ($request->hasFile('afbeelding')) { if ($request->hasFile('afbeelding')) {
$workExperience->clearMediaCollection('afbeelding'); $workExperience->clearMediaCollection('image');
$workExperience->addMediaFromRequest('afbeelding')->toMediaCollection('afbeelding'); $workExperience->addMediaFromRequest('afbeelding')->toMediaCollection('image');
} }
return redirect()->route('work-experiences.index')->with('success', 'Ervaring bijgewerkt.'); return redirect()->route('work-experiences.index')->with('success', 'Ervaring bijgewerkt.');

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
class NotifyTelegramAboutContactMessage implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected string $name;
protected string $message;
protected string $ip;
protected string $userAgent;
protected string $email;
protected string $phone;
public function __construct(string $name, string $message, string $ip, string $userAgent, ?string $email = null, ?string $phone = null)
{
$this->name = $name;
$this->message = $message;
$this->ip = $ip;
$this->userAgent = $userAgent;
$this->email = $email;
$this->phone = $phone;
}
public function handle()
{
$email = $this->email ?? '';
$phone = $this->phone ?? '';
$text = <<<TEXT
📩 *Nieuw contactbericht ontvangen*
👤 Naam: *{$this->name}*
💬 Bericht:
{$this->message}
📧 Email: {$email}
📱 Telefoon: {$phone}
🌐 IP: {$this->ip}
🧭 User Agent: `{$this->userAgent}`
🕒 Tijdstip: *{now()->format('d-m-Y H:i')}*
TEXT;
Http::post("https://api.telegram.org/bot" . config('services.telegram.bot_token') . "/sendMessage", [
'chat_id' => config('services.telegram.chat_id'),
'text' => $text,
'parse_mode' => 'Markdown',
]);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Jobs;
use App\Models\Personalia;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
class NotifyTelegramAboutPersonaliaClick implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $personalia;
protected $ip;
protected $userAgent;
public function __construct(Personalia $personalia, $ip, $userAgent)
{
$this->personalia = $personalia;
$this->ip = $ip;
$this->userAgent = $userAgent;
}
public function handle()
{
$message = <<<TEXT
👤 *Persoonlijke gegevens bekeken*
Naam: {$this->personalia->value}
IP: {$this->ip}
User Agent: `{$this->userAgent}`
📅 Tijdstip: *{$this->personalia->updated_at->format('d-m-Y H:i')}*
TEXT;
Http::post("https://api.telegram.org/bot" . config('services.telegram.bot_token') . "/sendMessage", [
'chat_id' => config('services.telegram.chat_id'),
'text' => $message,
'parse_mode' => 'Markdown',
]);
}
}

View File

@@ -16,4 +16,13 @@ class Education extends Model implements HasMedia
'einddatum', 'einddatum',
'beschrijving', 'beschrijving',
]; ];
public function image()
{
return $this->getFirstMedia('image');
}
public function imageUrl()
{
return $this->image() ? $this->image()->getUrl() : null;
}
} }

View File

@@ -10,4 +10,13 @@ class Personalia extends Model
protected $casts = [ protected $casts = [
'hidden' => 'boolean', 'hidden' => 'boolean',
]; ];
public function image()
{
return $this->getFirstMedia('image');
}
public function imageUrl()
{
return $this->image() ? $this->image()->getUrl() : null;
}
} }

View File

@@ -11,5 +11,14 @@ class Skill extends Model implements HasMedia
{ {
use InteractsWithMedia; use InteractsWithMedia;
protected $fillable = ['title', 'description', 'rating']; protected $fillable = ['title', 'description', 'rating', 'type'];
public function image()
{
return $this->getFirstMedia('image');
}
public function imageUrl()
{
return $this->image() ? $this->image()->getUrl() : null;
}
} }

View File

@@ -18,5 +18,14 @@ class WorkExperience extends Model implements HasMedia
'beschrijving', 'beschrijving',
]; ];
public function image()
{
return $this->getFirstMedia('image');
}
public function imageUrl()
{
return $this->image() ? $this->image()->getUrl() : null;
}
// Als je mediaconversies of image handling wil: hier kun je die later toevoegen // Als je mediaconversies of image handling wil: hier kun je die later toevoegen
} }

View File

@@ -34,5 +34,9 @@ return [
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
], ],
], ],
'telegram' => [
'bot_token' => env('TELEGRAM_BOT_TOKEN'),
'chat_id' => env('TELEGRAM_CHAT_ID'),
],
]; ];

View File

@@ -0,0 +1,32 @@
[
{
"id": "1",
"opleiding": "Havo",
"instituut": "Blaise pascal (Scala rietvelden)",
"startdatum": "2006-07-01",
"einddatum": "2013-01-07",
"beschrijving": "Mijn middelbare schoolperiode kende een hobbelig begin: ik startte op het havo/vwo, maar had in de eerste jaren moeite met motivatie en discipline. Daardoor ben ik afgezakt naar het mavo-niveau. Op de mavo heb ik mezelf herpakt en mijn focus hervonden. Dankzij die ommekeer kon ik succesvol doorstromen naar de havo, waar ik mijn diploma met ruime voldoendes heb behaald. Deze periode heeft mij geleerd hoe belangrijk eigen inzet en doelgerichtheid zijn.",
"created_at": "2025-06-18 23:08:02",
"updated_at": "2025-06-18 23:20:19"
},
{
"id": "2",
"opleiding": "Internation Business and Management Studies",
"instituut": "Hogeschool Rotterdam",
"startdatum": "2013-07-01",
"einddatum": "2014-07-01",
"beschrijving": "International Business and Management Studies is een brede economische hbo-opleiding gericht op internationale handel, marketing en bedrijfsvoering. Tijdens het eerste jaar maakte ik kennis met onderwerpen als exportstrategieën, interculturele communicatie en organisatiekunde. Hoewel ik waardevolle inzichten heb opgedaan, merkte ik al snel dat de inhoud en aanpak van de opleiding niet goed aansloten bij mij.",
"created_at": "2025-06-18 23:11:45",
"updated_at": "2025-06-18 23:11:45"
},
{
"id": "3",
"opleiding": "HBO Business Studies (Specialisering logistiek)",
"instituut": "Inholland Rotterdam",
"startdatum": "2014-07-01",
"einddatum": "2017-07-01",
"beschrijving": "Tijdens deze hbo-opleiding verdiepte ik mij in bedrijfskunde, ondernemerschap en supply chain management. In de laatste fase volgde ik de specialisatie Logistiek aan de vestiging in Haarlem, met een sterke focus op luchtvaartlogistiek en internationale goederenstromen. Hoewel ik brede kennis heb opgedaan binnen het vakgebied, heb ik de opleiding uiteindelijk voortijdig moeten beëindigen. In 2016 startte ik mijn eigen bedrijf, dat in korte tijd sterk groeide. Het combineren van een fulltime afstudeerstage met mijn ondernemersverantwoordelijkheden bleek op dat moment niet haalbaar.",
"created_at": "2025-06-18 23:17:49",
"updated_at": "2025-06-18 23:17:49"
}
]

762
database/data/media.json Normal file
View File

@@ -0,0 +1,762 @@
[
{
"id": "1",
"model_type": "App\\Models\\WorkExperience",
"model_id": "1",
"uuid": "3aa34696-9a24-4efe-a255-71fb23093bdb",
"collection_name": "image",
"name": "jumbo",
"file_name": "jumbo.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "3828",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 17:58:25",
"updated_at": "2025-07-08 17:58:25"
},
{
"id": "2",
"model_type": "App\\Models\\Skill",
"model_id": "1",
"uuid": "913531cd-8948-45a6-9f8e-abdcfed30677",
"collection_name": "image",
"name": "php-logo",
"file_name": "php-logo.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "80608",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:22:47",
"updated_at": "2025-07-08 19:22:47"
},
{
"id": "3",
"model_type": "App\\Models\\WorkExperience",
"model_id": "2",
"uuid": "da630aba-72a4-4dbe-8deb-95d994adde75",
"collection_name": "image",
"name": "sitiweb-logo",
"file_name": "sitiweb-logo.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "2296",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 18:22:18",
"updated_at": "2025-07-08 18:22:18"
},
{
"id": "4",
"model_type": "App\\Models\\Skill",
"model_id": "3",
"uuid": "486f344f-dd27-4636-904a-4a5686a06d42",
"collection_name": "image",
"name": "5848152fcef1014c0b5e4967",
"file_name": "5848152fcef1014c0b5e4967.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "9955",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:25:31",
"updated_at": "2025-07-08 19:25:31"
},
{
"id": "5",
"model_type": "App\\Models\\Skill",
"model_id": "2",
"uuid": "73a14a0b-4f75-41c4-933b-63c107492162",
"collection_name": "image",
"name": "JavaScript",
"file_name": "JavaScript.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "5418",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:35:02",
"updated_at": "2025-07-08 19:35:02"
},
{
"id": "6",
"model_type": "App\\Models\\Education",
"model_id": "1",
"uuid": "b8166354-3e19-4eb5-8778-5f160c5528c0",
"collection_name": "image",
"name": "images",
"file_name": "images.jpg",
"mime_type": "image/jpeg",
"disk": "public",
"conversions_disk": "public",
"size": "10052",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 18:01:03",
"updated_at": "2025-07-08 18:01:03"
},
{
"id": "7",
"model_type": "App\\Models\\WorkExperience",
"model_id": "3",
"uuid": "8b814d2e-15f3-4f73-b6b4-ff5114346f63",
"collection_name": "image",
"name": "internettoday-logo",
"file_name": "internettoday-logo.jpg",
"mime_type": "image/jpeg",
"disk": "public",
"conversions_disk": "public",
"size": "13085",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 18:29:44",
"updated_at": "2025-07-08 18:29:44"
},
{
"id": "8",
"model_type": "App\\Models\\Education",
"model_id": "2",
"uuid": "0af4f477-13a0-4926-988f-44cfd886b88e",
"collection_name": "image",
"name": "hr",
"file_name": "hr.jpg",
"mime_type": "image/jpeg",
"disk": "public",
"conversions_disk": "public",
"size": "4159",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 18:00:56",
"updated_at": "2025-07-08 18:00:56"
},
{
"id": "9",
"model_type": "App\\Models\\Skill",
"model_id": "4",
"uuid": "f3d64062-0571-40db-8871-18584a45420e",
"collection_name": "image",
"name": "linux-logo",
"file_name": "linux-logo.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "145726",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:26:24",
"updated_at": "2025-07-08 19:26:24"
},
{
"id": "10",
"model_type": "App\\Models\\Education",
"model_id": "3",
"uuid": "3d811274-5a83-4b56-99af-d87a9d404fc5",
"collection_name": "image",
"name": "inh",
"file_name": "inh.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "3053",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 18:00:48",
"updated_at": "2025-07-08 18:00:48"
},
{
"id": "11",
"model_type": "App\\Models\\Skill",
"model_id": "5",
"uuid": "58b96151-814b-4b83-a775-f64101f046a9",
"collection_name": "image",
"name": "git-logo",
"file_name": "git-logo.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "2383",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 17:12:48",
"updated_at": "2025-07-08 17:12:48"
},
{
"id": "12",
"model_type": "App\\Models\\Skill",
"model_id": "7",
"uuid": "05cd0535-e055-434d-b025-c9c855651dd3",
"collection_name": "image",
"name": "lara",
"file_name": "lara.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "37110",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:21:24",
"updated_at": "2025-07-08 19:21:24"
},
{
"id": "13",
"model_type": "App\\Models\\Skill",
"model_id": "8",
"uuid": "65d02709-dc31-4ccf-84da-7e0ac58c62bc",
"collection_name": "image",
"name": "Livewire-logo",
"file_name": "Livewire-logo.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "32291",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:27:21",
"updated_at": "2025-07-08 19:27:21"
},
{
"id": "14",
"model_type": "App\\Models\\Skill",
"model_id": "9",
"uuid": "7e04527d-91c8-4017-9f58-31f9b209e5a8",
"collection_name": "image",
"name": "Tailwind CSS",
"file_name": "Tailwind-CSS.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "6255",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:29:55",
"updated_at": "2025-07-08 19:29:55"
},
{
"id": "15",
"model_type": "App\\Models\\Skill",
"model_id": "10",
"uuid": "428f92d1-918a-4490-a2ac-ec613c71b073",
"collection_name": "image",
"name": "Postman",
"file_name": "Postman.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "8252",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:34:47",
"updated_at": "2025-07-08 19:34:47"
},
{
"id": "16",
"model_type": "App\\Models\\Skill",
"model_id": "11",
"uuid": "a2ff85d9-e3e6-4e12-ad9d-66b632bd187d",
"collection_name": "image",
"name": "Docker",
"file_name": "Docker.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "11082",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:34:31",
"updated_at": "2025-07-08 19:34:31"
},
{
"id": "17",
"model_type": "App\\Models\\Skill",
"model_id": "12",
"uuid": "7b827656-bef3-44eb-bb70-53e4b77b9148",
"collection_name": "image",
"name": "proxmox-logo",
"file_name": "proxmox-logo.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "20014",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:33:25",
"updated_at": "2025-07-08 19:33:25"
},
{
"id": "18",
"model_type": "App\\Models\\Skill",
"model_id": "13",
"uuid": "12ce5e7b-fc63-4657-a2ff-edef35d4bb78",
"collection_name": "image",
"name": "Grafana",
"file_name": "Grafana.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "12920",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:34:14",
"updated_at": "2025-07-08 19:34:14"
},
{
"id": "19",
"model_type": "App\\Models\\Skill",
"model_id": "14",
"uuid": "496ad2ef-7df9-42b8-b7ed-6e4424621b77",
"collection_name": "image",
"name": "Cloudflare",
"file_name": "Cloudflare.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "7363",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:35:34",
"updated_at": "2025-07-08 19:35:34"
},
{
"id": "20",
"model_type": "App\\Models\\Skill",
"model_id": "15",
"uuid": "019cc0f2-57b9-4e94-b307-ecb69988ed00",
"collection_name": "image",
"name": "directadmnin-logo",
"file_name": "directadmnin-logo.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "2364",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:37:04",
"updated_at": "2025-07-08 19:37:04"
},
{
"id": "21",
"model_type": "App\\Models\\Skill",
"model_id": "16",
"uuid": "197de2dd-42cb-469e-bd5d-b9d2692ddede",
"collection_name": "image",
"name": "MySQL",
"file_name": "MySQL.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "9330",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:37:57",
"updated_at": "2025-07-08 19:37:57"
},
{
"id": "22",
"model_type": "App\\Models\\Skill",
"model_id": "17",
"uuid": "c5fed034-9745-4fc3-84c0-720372e6ea56",
"collection_name": "image",
"name": "Uptime-Kuma-Logo",
"file_name": "Uptime-Kuma-Logo.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "36536",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:38:36",
"updated_at": "2025-07-08 19:38:36"
},
{
"id": "23",
"model_type": "App\\Models\\Skill",
"model_id": "18",
"uuid": "2e362926-027a-4e8a-93bd-9a27e9e0fc18",
"collection_name": "image",
"name": "NGINX",
"file_name": "NGINX.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "5301",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:39:19",
"updated_at": "2025-07-08 19:39:19"
},
{
"id": "24",
"model_type": "App\\Models\\Skill",
"model_id": "19",
"uuid": "22a353cd-410a-484f-8362-09a00d97a183",
"collection_name": "image",
"name": "Visual Studio Code (VS Code)",
"file_name": "Visual-Studio-Code-(VS-Code).png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "16772",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:40:32",
"updated_at": "2025-07-08 19:40:32"
},
{
"id": "25",
"model_type": "App\\Models\\Skill",
"model_id": "20",
"uuid": "d1e09302-0730-493f-9b35-a6f82b27e7b6",
"collection_name": "image",
"name": "GIMP",
"file_name": "GIMP.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "32519",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:41:59",
"updated_at": "2025-07-08 19:41:59"
},
{
"id": "26",
"model_type": "App\\Models\\Skill",
"model_id": "21",
"uuid": "1d7d73e4-f373-485a-9205-7261869de4b0",
"collection_name": "image",
"name": "WordPress",
"file_name": "WordPress.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "21613",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:42:37",
"updated_at": "2025-07-08 19:42:37"
},
{
"id": "27",
"model_type": "App\\Models\\Skill",
"model_id": "22",
"uuid": "9e062f71-7e0b-4bb0-b455-c4b954a607a8",
"collection_name": "image",
"name": "WooCommerce",
"file_name": "WooCommerce.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "8433",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:43:00",
"updated_at": "2025-07-08 19:43:00"
},
{
"id": "28",
"model_type": "App\\Models\\Skill",
"model_id": "23",
"uuid": "dda5797d-16c3-4a70-bf4d-e3eded1d1003",
"collection_name": "image",
"name": "Vaultwarden--Streamline-Simple-Icons",
"file_name": "Vaultwarden--Streamline-Simple-Icons.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "7139",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:44:30",
"updated_at": "2025-07-08 19:44:30"
},
{
"id": "29",
"model_type": "App\\Models\\Skill",
"model_id": "24",
"uuid": "c981f25b-b486-4b1d-a4f3-5a15d85464ac",
"collection_name": "image",
"name": "healthchecks.logo",
"file_name": "healthchecks.logo.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "11561",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:45:12",
"updated_at": "2025-07-08 19:45:12"
},
{
"id": "30",
"model_type": "App\\Models\\Skill",
"model_id": "25",
"uuid": "606ecdbf-9ed5-4853-b8eb-405bc35a120d",
"collection_name": "image",
"name": "GitLab",
"file_name": "GitLab.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "24046",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:46:14",
"updated_at": "2025-07-08 19:46:14"
},
{
"id": "31",
"model_type": "App\\Models\\Skill",
"model_id": "26",
"uuid": "3be7bb80-74a6-444d-b2ef-b072900fc03d",
"collection_name": "image",
"name": "GitHub",
"file_name": "GitHub.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "8449",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:46:27",
"updated_at": "2025-07-08 19:46:27"
},
{
"id": "32",
"model_type": "App\\Models\\Skill",
"model_id": "27",
"uuid": "939e8d71-73c4-41ea-ae35-6132cecfdc3c",
"collection_name": "image",
"name": "Fedora",
"file_name": "Fedora.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "10591",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:46:54",
"updated_at": "2025-07-08 19:46:54"
},
{
"id": "33",
"model_type": "App\\Models\\Skill",
"model_id": "28",
"uuid": "671081cf-b852-41ff-a746-835f75beaa46",
"collection_name": "image",
"name": "Ubuntu",
"file_name": "Ubuntu.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "11230",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:47:15",
"updated_at": "2025-07-08 19:47:15"
},
{
"id": "34",
"model_type": "App\\Models\\Skill",
"model_id": "29",
"uuid": "0c968a1c-202c-4635-b613-04874733d994",
"collection_name": "image",
"name": "CentOS",
"file_name": "CentOS.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "11745",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:47:43",
"updated_at": "2025-07-08 19:47:43"
},
{
"id": "35",
"model_type": "App\\Models\\Skill",
"model_id": "30",
"uuid": "1e78c7c9-d73f-4ea8-9426-b274d2b63279",
"collection_name": "image",
"name": "Debian",
"file_name": "Debian.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "6999",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:48:14",
"updated_at": "2025-07-08 19:48:14"
},
{
"id": "36",
"model_type": "App\\Models\\Skill",
"model_id": "31",
"uuid": "b36a3c4f-d19e-4832-b0b9-9728ce850f45",
"collection_name": "image",
"name": "Composer",
"file_name": "Composer.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "31190",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:48:40",
"updated_at": "2025-07-08 19:48:40"
},
{
"id": "37",
"model_type": "App\\Models\\Skill",
"model_id": "32",
"uuid": "6a888cd6-982b-4e1a-87c8-fd2a200f8fc5",
"collection_name": "image",
"name": "NPM",
"file_name": "NPM.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "1136",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:49:09",
"updated_at": "2025-07-08 19:49:09"
},
{
"id": "38",
"model_type": "App\\Models\\Skill",
"model_id": "33",
"uuid": "fb6f3d4d-9a4b-4c4a-87a9-09b9c9bb58c9",
"collection_name": "image",
"name": "HTML5",
"file_name": "HTML5.png",
"mime_type": "image/png",
"disk": "public",
"conversions_disk": "public",
"size": "31694",
"manipulations": [],
"custom_properties": [],
"generated_conversions": [],
"responsive_images": [],
"order_column": 1,
"created_at": "2025-07-08 19:50:02",
"updated_at": "2025-07-08 19:50:02"
}
]

View File

@@ -0,0 +1,29 @@
[
{
"id": "1",
"key": "Email",
"value": "roberto@guagliardo.nl",
"hidden": 1,
"icon": "fa-solid fa-envelope",
"created_at": "2025-06-18 20:24:49",
"updated_at": "2025-07-08 18:47:19"
},
{
"id": "2",
"key": "Telefoonnummer",
"value": "+ 31 6 444 60 893",
"hidden": 1,
"icon": "fa-solid fa-phone",
"created_at": "2025-06-18 22:43:12",
"updated_at": "2025-06-18 22:43:12"
},
{
"id": "3",
"key": "Adres",
"value": "Hoefsmid 16<br>3201TC Spijkenisse",
"hidden": 1,
"icon": "fa-solid fa-home",
"created_at": "2025-06-18 22:50:09",
"updated_at": "2025-06-18 22:50:09"
}
]

353
database/data/skills.json Normal file
View File

@@ -0,0 +1,353 @@
[
{
"id": "2",
"type": "rating",
"title": "PHP",
"description": "Met meer dan 9 jaar ervaring in PHP, WordPress en Laravel, heb ik brede expertise opgebouwd in het ontwikkelen van zowel maatwerkfuncties als complete webapplicaties.",
"rating": 9,
"created_at": "2025-07-08 16:49:03",
"updated_at": "2025-07-08 17:14:02"
},
{
"id": "3",
"type": "rating",
"title": "JavaScript",
"description": "Brede ervaring met JavaScript, van vanilla JS tot moderne frameworks zoals React inzetbaar voor zowel interactieve interfaces als volledige front-end architecturen.",
"rating": 7,
"created_at": "2025-07-08 16:54:03",
"updated_at": "2025-07-08 16:54:03"
},
{
"id": "4",
"type": "rating",
"title": "Python",
"description": "Ruime ervaring met Python voor het bouwen van tools en automatiseringen: van beeldherkenning en muisbesturing tot webscrapers, image-optimalisatie en importtools voor webshops.",
"rating": 7,
"created_at": "2025-07-08 17:00:01",
"updated_at": "2025-07-08 17:00:01"
},
{
"id": "5",
"type": "rating",
"title": "Linux & Serverbeheer",
"description": "Ervaren in het opzetten, beheren en automatiseren van Linux-omgevingen. Dagelijks gebruik van Fedora (werkstation), en zakelijk gewerkt met CentOS, AlmaLinux en Debian. Bekwaam in Bash-scripting, serveronderhoud, optimalisatie en het draaien van hostingplatformen.",
"rating": 8,
"created_at": "2025-07-08 17:02:21",
"updated_at": "2025-07-08 20:11:47"
},
{
"id": "7",
"type": "rating",
"title": "DevOps & Tools",
"description": "Ervaring met Git, Docker, Nginx, SSH en cronjobs. Bekend met CI/CD-principes en inzet van tooling voor automatisering en monitoring binnen ontwikkel- en hostingomgevingen.",
"rating": 8,
"created_at": "2025-07-08 17:12:48",
"updated_at": "2025-07-08 17:12:48"
},
{
"id": "9",
"type": "tag",
"title": "Leergierig",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:08:00",
"updated_at": "2025-07-08 19:08:00"
},
{
"id": "10",
"type": "other",
"title": "Laravel",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:19:00",
"updated_at": "2025-07-08 19:19:00"
},
{
"id": "11",
"type": "other",
"title": "LiveWire",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:27:21",
"updated_at": "2025-07-08 19:27:21"
},
{
"id": "12",
"type": "other",
"title": "Tailwind",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:29:55",
"updated_at": "2025-07-08 19:29:55"
},
{
"id": "13",
"type": "other",
"title": "Postman",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:32:42",
"updated_at": "2025-07-08 19:32:42"
},
{
"id": "14",
"type": "other",
"title": "Docker",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:32:55",
"updated_at": "2025-07-08 19:32:55"
},
{
"id": "15",
"type": "other",
"title": "Proxmox",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:33:25",
"updated_at": "2025-07-08 19:33:25"
},
{
"id": "16",
"type": "other",
"title": "Grafana",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:34:14",
"updated_at": "2025-07-08 19:34:14"
},
{
"id": "17",
"type": "other",
"title": "Cloudflare",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:35:34",
"updated_at": "2025-07-08 19:35:34"
},
{
"id": "18",
"type": "other",
"title": "directadmin",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:37:04",
"updated_at": "2025-07-08 19:37:04"
},
{
"id": "19",
"type": "other",
"title": "MySQL/MariaDB",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:37:57",
"updated_at": "2025-07-08 19:37:57"
},
{
"id": "20",
"type": "other",
"title": "Uptime Kuma",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:38:36",
"updated_at": "2025-07-08 19:38:36"
},
{
"id": "21",
"type": "other",
"title": "Nginx",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:39:19",
"updated_at": "2025-07-08 19:39:19"
},
{
"id": "22",
"type": "other",
"title": "VS code",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:40:32",
"updated_at": "2025-07-08 19:40:32"
},
{
"id": "23",
"type": "other",
"title": "GIMP",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:41:59",
"updated_at": "2025-07-08 19:41:59"
},
{
"id": "24",
"type": "other",
"title": "WordPress",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:42:37",
"updated_at": "2025-07-08 19:42:37"
},
{
"id": "25",
"type": "other",
"title": "WooCommerce",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:43:00",
"updated_at": "2025-07-08 19:43:00"
},
{
"id": "26",
"type": "other",
"title": "Vaultwarden",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:44:30",
"updated_at": "2025-07-08 19:44:30"
},
{
"id": "27",
"type": "other",
"title": "HealthChecks",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:45:12",
"updated_at": "2025-07-08 19:45:12"
},
{
"id": "28",
"type": "other",
"title": "GitLab",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:46:14",
"updated_at": "2025-07-08 19:46:14"
},
{
"id": "29",
"type": "other",
"title": "GitHub",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:46:27",
"updated_at": "2025-07-08 19:46:27"
},
{
"id": "30",
"type": "other",
"title": "Fedora",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:46:54",
"updated_at": "2025-07-08 19:46:54"
},
{
"id": "31",
"type": "other",
"title": "Ubuntu",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:47:15",
"updated_at": "2025-07-08 19:47:15"
},
{
"id": "32",
"type": "other",
"title": "CentOS",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:47:43",
"updated_at": "2025-07-08 19:47:51"
},
{
"id": "33",
"type": "other",
"title": "Debian",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:48:14",
"updated_at": "2025-07-08 19:48:14"
},
{
"id": "34",
"type": "other",
"title": "Composer",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:48:40",
"updated_at": "2025-07-08 19:48:40"
},
{
"id": "35",
"type": "other",
"title": "NPM",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:49:09",
"updated_at": "2025-07-08 19:49:09"
},
{
"id": "36",
"type": "other",
"title": "HTML",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:50:02",
"updated_at": "2025-07-08 19:50:02"
},
{
"id": "37",
"type": "tag",
"title": "Klantgericht",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:50:46",
"updated_at": "2025-07-08 19:50:46"
},
{
"id": "38",
"type": "tag",
"title": "Dekend aan oplossingen",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:51:09",
"updated_at": "2025-07-08 19:51:09"
},
{
"id": "39",
"type": "tag",
"title": "Meedenkend",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:51:37",
"updated_at": "2025-07-08 19:51:37"
},
{
"id": "40",
"type": "tag",
"title": "Probleemoplossend",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:52:22",
"updated_at": "2025-07-08 19:52:22"
},
{
"id": "41",
"type": "tag",
"title": "Initiatiefrijk",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:52:45",
"updated_at": "2025-07-08 19:52:45"
},
{
"id": "42",
"type": "tag",
"title": "Systeemdenker",
"description": null,
"rating": 5,
"created_at": "2025-07-08 19:53:28",
"updated_at": "2025-07-08 19:53:28"
}
]

View File

@@ -0,0 +1,32 @@
[
{
"id": "1",
"werkgever": "Jumbo Supermarkten",
"functie": "Teamleider",
"startdatum": "2010-01-01",
"einddatum": "2018-12-31",
"beschrijving": "Na mijn start als bijbaanmedewerker ben ik binnen een jaar doorgegroeid naar de rol van teamleider. In deze functie werd ik door de regiomanager actief ingezet op verschillende filialen met een extra behoefte aan structuur, klantgerichtheid en kwaliteitsverbetering. Dit betrof zowel nieuwe vestigingen als filialen waar de bestaande werkmentaliteit niet strookte met de kwaliteitsnormen van Jumbo.\r\n\r\nMijn aanpak was altijd gericht op het verhogen van klanttevredenheid, het verbeteren van interne processen en het motiveren van collegas. Dankzij mijn inzet en flexibiliteit heb ik meerdere filialen kunnen ondersteunen in het opzetten of herstructureren van een sterk, klantgericht team.",
"created_at": "2025-06-18 18:13:10",
"updated_at": "2025-06-18 18:13:10"
},
{
"id": "2",
"werkgever": "SitiWeb",
"functie": "Eigenaar / Technisch Specialist",
"startdatum": "2016-08-01",
"einddatum": "2024-10-01",
"beschrijving": "Als oprichter en eigenaar van Sitiweb heb ik gedurende acht jaar totaaloplossingen geleverd aan ondernemers met websites en webshops. De focus lag op zowel het verbeteren van websites als het technisch optimaliseren van hostingomgevingen.\r\n\r\nIk was verantwoordelijk voor de technische infrastructuur, het opzetten en beheren van hostingomgevingen (inclusief VPS-servers), en het uitvoeren van maatwerkoptimalisaties. Daarnaast adviseerde ik klanten actief over verbeteringen op het gebied van snelheid, veiligheid en gebruikservaring.\r\n\r\nOnder mijn leiding groeide Sitiweb uit tot een gewaardeerde partij binnen het MKB, met een sterke focus op kwaliteit en persoonlijke service. In 2024 heb ik besloten om een stap terug te doen, omdat het bedrijf dusdanig was gegroeid dat het met een klein team niet langer optimaal beheersbaar was.",
"created_at": "2025-07-08 18:22:12",
"updated_at": "2025-07-08 18:22:12"
},
{
"id": "3",
"werkgever": "InternetToday",
"functie": "Senior Developer",
"startdatum": "2024-10-01",
"einddatum": null,
"beschrijving": "Binnen InternetToday vervul ik een sleutelrol als senior developer met een brede technische verantwoordelijkheid. Ik heb de werkstructuur binnen het ontwikkelteam geoptimaliseerd door het opzetten van uitgebreide monitoring- en analysetools, wat direct heeft bijgedragen aan een efficiëntere en kwalitatievere werkwijze binnen het team.\r\n\r\nDaarnaast fungeer ik als brug tussen de developmentafdeling en de serverbeheerafdeling. Dankzij mijn diepgaande kennis van Linux, hosting en infrastructuur ben ik in staat om complexe technische vraagstukken snel op te lossen en oplossingen te ontwikkelen die doorgaans buiten de scope van het developmentteam vallen. Deze veelzijdigheid maakt mij een waardevolle schakel binnen het bedrijf.",
"created_at": "2025-07-08 18:29:36",
"updated_at": "2025-07-08 18:31:59"
}
]

View File

@@ -0,0 +1,26 @@
<?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()
{
Schema::table('skills', function (Blueprint $table) {
$table->string('type')->default('rating')->after('id');
});
}
public function down()
{
Schema::table('skills', function (Blueprint $table) {
$table->dropColumn('type');
});
}
};

View File

@@ -0,0 +1,38 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
class EducationSeeder extends Seeder
{
public function run(): void
{
$path = database_path('data/education.json');
if (!File::exists($path)) {
return;
}
// Verwijder alle bestaande records
DB::table('education')->truncate();
// Laad nieuwe data in
$data = json_decode(File::get($path), true);
foreach ($data as $item) {
DB::table('education')->insert([
'id' => $item['id'],
'opleiding' => $item['opleiding'],
'instituut' => $item['instituut'],
'startdatum' => $item['startdatum'],
'einddatum' => $item['einddatum'] ?? null,
'beschrijving' => $item['beschrijving'],
'created_at' => $item['created_at'] ?? now(),
'updated_at' => $item['updated_at'] ?? now(),
]);
}
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
class MediaSeeder extends Seeder
{
public function run(): void
{
$path = database_path('data/media.json');
if (!File::exists($path)) {
$this->command->warn("media.json niet gevonden, seeding overgeslagen.");
return;
}
DB::table('media')->truncate();
$data = json_decode(File::get($path), true);
foreach ($data as $item) {
DB::table('media')->insert([
// 'id' => $item['id'],
'model_type' => $item['model_type'],
'model_id' => $item['model_id'],
'uuid' => $item['uuid'],
'collection_name' => $item['collection_name'],
'name' => $item['name'],
'file_name' => $item['file_name'],
'mime_type' => $item['mime_type'],
'disk' => $item['disk'],
'conversions_disk' => $item['conversions_disk'],
'size' => $item['size'],
'manipulations' => json_encode($item['manipulations']),
'custom_properties' => json_encode($item['custom_properties']),
'generated_conversions' => json_encode($item['generated_conversions']),
'responsive_images' => json_encode($item['responsive_images']),
'order_column' => $item['order_column'] ?? null,
'created_at' => $item['created_at'] ?? now(),
'updated_at' => $item['updated_at'] ?? now(),
]);
}
$this->command->info('Media succesvol geïmporteerd.');
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\Personalia;
use Illuminate\Support\Facades\File;
class PersonaliaSeeder extends Seeder
{
public function run(): void
{
$path = database_path('data/personalia.json');
if (!File::exists($path)) {
$this->command->warn("Bestand {$path} bestaat niet, seeder overgeslagen.");
return;
}
// Leegmaken van de bestaande data
Personalia::truncate();
// JSON inladen
$data = json_decode(File::get($path), true);
// Records toevoegen
foreach ($data as $item) {
Personalia::create([
'key' => $item['key'],
'value' => $item['value'],
'hidden' => $item['hidden'],
'icon' => $item['icon'],
]);
}
$this->command->info(count($data) . ' personalia-records geïmporteerd.');
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\Skill;
use Illuminate\Support\Facades\File;
class SkillSeeder extends Seeder
{
public function run(): void
{
$path = database_path('data/skills.json');
if (!File::exists($path)) {
return;
}
// Verwijder alle bestaande records
Skill::truncate();
// Laad en decode de JSON
$data = json_decode(File::get($path), true);
// Voeg nieuwe data toe
foreach ($data as $item) {
Skill::create([
'type' => $item['type'],
'title' => $item['title'],
'description' => $item['description'] ?? null,
'rating' => $item['rating'] ?? null,
'created_at' => $item['created_at'] ?? now(),
'updated_at' => $item['updated_at'] ?? now(),
]);
}
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\WorkExperience;
use Illuminate\Support\Facades\File;
class WorkExperienceSeeder extends Seeder
{
public function run(): void
{
$jsonPath = database_path('data/work_experiences.json');
// Bestaat het JSON-bestand?
if (!File::exists($jsonPath)) {
$this->command->warn("❌ Bestand $jsonPath niet gevonden. Seeder overgeslagen.");
return;
}
// Verwijder bestaande records
WorkExperience::truncate();
// Lees en decode de JSON
$json = File::get($jsonPath);
$data = json_decode($json, true);
// Voeg werkervaringen toe
foreach ($data as $item) {
WorkExperience::updateOrCreate(
[
'werkgever' => $item['werkgever'],
'functie' => $item['functie'],
'startdatum' => $item['startdatum'],
],
[
'einddatum' => $item['einddatum'] ?? null,
'beschrijving' => $item['beschrijving'],
]
);
}
$this->command->info("✅ Werkervaringen succesvol geïmporteerd.");
}
}

9
package-lock.json generated
View File

@@ -4,6 +4,9 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"dependencies": {
"stickybits": "^3.7.11"
},
"devDependencies": { "devDependencies": {
"@tailwindcss/forms": "^0.5.2", "@tailwindcss/forms": "^0.5.2",
"@tailwindcss/vite": "^4.0.0", "@tailwindcss/vite": "^4.0.0",
@@ -3259,6 +3262,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/stickybits": {
"version": "3.7.11",
"resolved": "https://registry.npmjs.org/stickybits/-/stickybits-3.7.11.tgz",
"integrity": "sha512-WO+ns7BYZqGS4jWVTg5JNhIvNV4LGbUtNTSck4zAkWRQzA1IfxwIkMGc0BbdGy4PGIjK7kKo5CZcN6Sd5dHVlw==",
"license": "MIT"
},
"node_modules/string-width": { "node_modules/string-width": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",

View File

@@ -17,5 +17,8 @@
"postcss": "^8.4.31", "postcss": "^8.4.31",
"tailwindcss": "^3.1.0", "tailwindcss": "^3.1.0",
"vite": "^6.2.4" "vite": "^6.2.4"
},
"dependencies": {
"stickybits": "^3.7.11"
} }
} }

View File

@@ -1,3 +1,24 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
#custom-cursor {
position: fixed;
top: 0;
left: 0;
pointer-events: none;
z-index: 9999;
height: 30px;
width: auto;
display: inline-block;
transform: scaleX(-1) translate(-50%, -50%);
transform-origin: top left;
}
#custom-cursor svg {
height: 100%;
width: auto;
display: block;
}

View File

@@ -5,3 +5,12 @@ import Alpine from 'alpinejs';
window.Alpine = Alpine; window.Alpine = Alpine;
Alpine.start(); Alpine.start();
import stickybits from 'stickybits';
document.addEventListener('DOMContentLoaded', () => {
stickybits('#right-content', {
stickyBitStickyOffset: 40,
parent: '.grid' // dit moet de container zijn waarin sticky moet blijven
});
});

View File

@@ -4,7 +4,7 @@
</div> </div>
@if (session('status') == 'verification-link-sent') @if (session('status') == 'verification-link-sent')
<div class="mb-4 font-medium text-sm text-green-600 dark:text-green-400"> <div class="mb-4 font-medium text-sm text-green-600 dark:text-sitiweb-green">
{{ __('A new verification link has been sent to the email address you provided during registration.') }} {{ __('A new verification link has been sent to the email address you provided during registration.') }}
</div> </div>
@endif @endif

View File

@@ -1,7 +1,7 @@
@props(['status']) @props(['status'])
@if ($status) @if ($status)
<div {{ $attributes->merge(['class' => 'font-medium text-sm text-green-600 dark:text-green-400']) }}> <div {{ $attributes->merge(['class' => 'font-medium text-sm text-green-600 dark:text-sitiweb-green']) }}>
{{ $status }} {{ $status }}
</div> </div>
@endif @endif

View File

@@ -11,12 +11,12 @@
<p class="text-sm text-gray-500 dark:text-gray-400">{{ $education->startdatum }} {{ $education->einddatum ?? 'heden' }}</p> <p class="text-sm text-gray-500 dark:text-gray-400">{{ $education->startdatum }} {{ $education->einddatum ?? 'heden' }}</p>
<p class="mt-2 text-gray-700 dark:text-gray-300">{{ $education->beschrijving }}</p> <p class="mt-2 text-gray-700 dark:text-gray-300">{{ $education->beschrijving }}</p>
@if ($education->getFirstMediaUrl('afbeelding')) @if ($education->image())
<img src="{{ $education->getFirstMediaUrl('afbeelding') }}" class="mt-4 w-32 h-auto rounded" /> <img src="{{ $education->imageUrl() }}" class="mt-4 w-32 h-auto rounded" />
@endif @endif
<div class="mt-4 space-x-2"> <div class="mt-4 space-x-2">
<a href="{{ route('educations.edit', $education) }}" class="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600">Bewerk</a> <a href="{{ route('educations.edit', $education) }}" class="px-3 py-1 bg-green-400 text-white rounded hover:bg-blue-600">Bewerk</a>
<form action="{{ route('educations.destroy', $education) }}" method="POST" class="inline-block" onsubmit="return confirm('Weet je zeker dat je dit wilt verwijderen?')"> <form action="{{ route('educations.destroy', $education) }}" method="POST" class="inline-block" onsubmit="return confirm('Weet je zeker dat je dit wilt verwijderen?')">
@csrf @csrf

View File

@@ -0,0 +1,24 @@
<div class="p-4 bg-white dark:bg-gray-800 rounded shadow">
<h3 class="text-lg font-bold text-gray-800 dark:text-white">{{ $skill->title }}</h3>
@if($skill->type === 'rating')
<p class="text-sm text-gray-600 dark:text-gray-300 mb-2">Beoordeling: {{ $skill->rating }}/10</p>
@endif
@if($skill->description)
<p class="text-sm text-gray-600 dark:text-gray-300">{{ $skill->description }}</p>
@endif
@if ($skill->getFirstMediaUrl('image'))
<img src="{{ $skill->getFirstMediaUrl('image') }}" alt="{{ $skill->title }}" class="mt-2 max-w-full h-32 object-contain rounded">
@endif
<div class="mt-4 flex justify-between">
<a href="{{ route('skills.edit', $skill) }}" class="text-blue-600 hover:underline">Bewerken</a>
<form action="{{ route('skills.destroy', $skill) }}" method="POST" onsubmit="return confirm('Weet je zeker dat je dit wilt verwijderen?')">
@csrf
@method('DELETE')
<button class="text-red-600 hover:underline">Verwijderen</button>
</form>
</div>
</div>

View File

@@ -0,0 +1,115 @@
<!-- Call to Action -->
<section
class="text-center lg:rounded-lg lg:bg-white lg:p-10 lg:shadow-xs lg:ring-1 lg:ring-zinc-950/5 dark:lg:bg-zinc-900 dark:lg:ring-white/10 space-y-4">
<h2 class="text-2xl font-semibold">
<i class="fa-solid fa-paper-plane mr-2 text-sitiweb-green"></i> Wat kan ik voor jou betekenen?
</h2>
<p class="text-gray-600 dark:text-gray-300 text-sm">
📬 Ook beschikbaar voor freelance opdrachten of samenwerkingen. <br>
</p>
<div class="flex justify-center gap-4 mt-4 flex-wrap">
<button onclick="openContactModal()"
class="px-5 py-2 bg-sitiweb-green text-white rounded-full text-sm font-medium hover:bg-green-700 transition">
<i class="fa-solid fa-envelope mr-2"></i> Contact opnemen
</button>
</div>
</section>
<!-- Contact Modal -->
<div id="contact-modal" class="fixed inset-0 z-50 hidden bg-black/50 flex items-center justify-center">
<div class="bg-white dark:bg-zinc-900 rounded-lg shadow-lg max-w-md w-full p-6 space-y-4">
<h3 class="text-xl font-semibold text-zinc-800 dark:text-white">Stuur een bericht</h3>
<form id="contact-form" class="space-y-4">
<div>
<label for="name" class="block text-sm font-medium text-zinc-700 dark:text-zinc-300">Naam</label>
<input type="text" id="name" name="name" required
class="w-full mt-1 px-3 py-2 border border-zinc-300 rounded-md focus:outline-none focus:ring-2 focus:ring-sitiweb-green dark:bg-zinc-800 dark:border-zinc-600 dark:text-white" />
</div>
<div>
<label for="email" class="block text-sm font-medium text-zinc-700 dark:text-zinc-300">E-mail
(optioneel)</label>
<input type="email" id="email" name="email"
class="w-full mt-1 px-3 py-2 border border-zinc-300 rounded-md focus:outline-none focus:ring-2 focus:ring-sitiweb-green dark:bg-zinc-800 dark:border-zinc-600 dark:text-white" />
</div>
<div>
<label for="phone" class="block text-sm font-medium text-zinc-700 dark:text-zinc-300">Telefoonnummer
(optioneel)</label>
<input type="tel" id="phone" name="phone"
class="w-full mt-1 px-3 py-2 border border-zinc-300 rounded-md focus:outline-none focus:ring-2 focus:ring-sitiweb-green dark:bg-zinc-800 dark:border-zinc-600 dark:text-white" />
</div>
<div>
<label for="message" class="block text-sm font-medium text-zinc-700 dark:text-zinc-300">Bericht</label>
<textarea id="message" name="message" required rows="4"
class="w-full mt-1 px-3 py-2 border border-zinc-300 rounded-md focus:outline-none focus:ring-2 focus:ring-sitiweb-green dark:bg-zinc-800 dark:border-zinc-600 dark:text-white"></textarea>
</div>
<div class="flex justify-end gap-2">
<button type="button" onclick="closeContactModal()"
class="px-4 py-2 text-sm bg-zinc-200 text-zinc-800 rounded-md hover:bg-zinc-300 dark:bg-zinc-700 dark:text-white dark:hover:bg-zinc-600">
Annuleren
</button>
<button type="submit"
class="px-4 py-2 text-sm bg-sitiweb-green text-white rounded-md hover:bg-green-700">
Versturen
</button>
</div>
</form>
</div>
</div>
@push('scripts')
<script>
function openContactModal() {
document.getElementById('contact-modal').classList.remove('hidden');
}
function closeContactModal() {
document.getElementById('contact-modal').classList.add('hidden');
}
document.getElementById('contact-form').addEventListener('submit', async function(e) {
e.preventDefault();
const name = document.getElementById('name').value.trim();
const message = document.getElementById('message').value.trim();
const email = document.getElementById('email').value.trim();
const phone = document.getElementById('phone').value.trim();
try {
const response = await fetch('/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute(
'content'),
},
body: JSON.stringify({
name,
message,
email,
phone
}),
});
if (response.ok) {
alert('Bericht succesvol verzonden!');
closeContactModal();
this.reset();
} else {
alert('Er ging iets mis bij het verzenden.');
}
} catch (error) {
console.error('Fout:', error);
alert('Netwerkfout bij verzenden.');
}
});
</script>
@endpush

View File

@@ -1,9 +1,19 @@
@foreach ($education as $item) @foreach ($education as $item)
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow space-y-2"> <hr role="presentation" class="w-full border-t border-zinc-950/10 dark:border-white/10 my-8">
<div class="space-y-2">
<div class="flex items-center gap-4">
@if ($item->image())
<img src="{{ $item->imageUrl() }}" alt="{{ $item->title }}"
class="w-12 h-12 rounded-md shadow-md object-contain bg-white dark:bg-gray-700 p-1">
@endif
<h3 class="text-xl font-bold text-gray-900 dark:text-white"> <h3 class="text-xl font-bold text-gray-900 dark:text-white">
{{ $item->opleiding }} {{ $item->opleiding }}
<span class="teitemxt-gray-500 dark:text-gray-400 font-normal"> {{ $item->instituut }}</span> <span class="teitemxt-gray-500 dark:text-gray-400 font-normal"> {{ $item->instituut }}</span>
</h3> </h3>
</div>
<p class="text-sm text-gray-600 dark:text-gray-300 italic"> <p class="text-sm text-gray-600 dark:text-gray-300 italic">
{{ \Carbon\Carbon::parse($item->startdatum)->translatedFormat('Y') }} {{ \Carbon\Carbon::parse($item->startdatum)->translatedFormat('Y') }}

View File

@@ -1,7 +1,7 @@
<ul class="space-y-4"> <ul class="space-y-4">
@foreach ($personalia as $item) @foreach ($personalia as $item)
<li class="flex items-start gap-4"> <li class="flex items-start gap-4 hover:scale-105 transition hover:shadow-lg">
<i class="fa fa-{{ $item->icon }} text-blue-600 text-xl mt-1"></i> <i class="fa fa-{{ $item->icon }} text-gray-800 dark:text-gray-400 text-xl mt-1"></i>
<div> <div>
<p class="text-sm font-semibold text-gray-700 dark:text-gray-200">{{ ucfirst($item->key) }}</p> <p class="text-sm font-semibold text-gray-700 dark:text-gray-200">{{ ucfirst($item->key) }}</p>
@@ -31,14 +31,9 @@
<button <button
class="text-gray-500 text-left dark:text-gray-400 hover:text-gray-800 dark:hover:text-white font-mono" class="text-gray-500 text-left dark:text-gray-400 hover:text-gray-800 dark:hover:text-white font-mono"
onclick="revealValue({{ $item->id }}, this)" onclick="revealValue({{ $item->id }}, this)" data-placeholder="{{ $masked }}">
data-placeholder="{{ $masked }}"
>
{{ $masked }} {{ $masked }}
</button> </button>
@else @else
<p class="text-gray-800 dark:text-white">{{ $item->value }}</p> <p class="text-gray-800 dark:text-white">{{ $item->value }}</p>
@endif @endif

View File

@@ -0,0 +1,66 @@
@if (isset($skills['tag']))
<div class="flex flex-wrap gap-2">
@foreach ($skills['tag'] as $skill)
<span
class="inline-block hover:scale-105 transition hover:shadow-lg bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-100 text-sm px-3 py-1 rounded">
{{ $skill->title }}
</span>
@endforeach
</div>
@endif
@if (isset($skills['rating']))
@foreach ($skills['rating'] as $skill)
<hr role="presentation" class="w-full border-t border-zinc-950/10 dark:border-white/10 my-8">
<div class="space-y-4 flex flex-col gap-4">
<div class="flex items-center gap-4">
@if ($skill->image())
<img src="{{ $skill->imageUrl() }}" alt="{{ $skill->title }}"
class="w-12 h-12 rounded-md shadow-md object-contain bg-white dark:bg-gray-700 p-1 hover:scale-125 transition hover:shadow-lg">
@endif
<h3 class="text-xl font-bold text-gray-900 dark:text-white">
{{ $skill->title }}
</h3>
</div>
{{-- Rating bar --}}
<div>
<div class="text-sm text-gray-600 dark:text-gray-300 italic mb-1">
Beoordeling: {{ $skill->rating }}/10
</div>
<div class="w-full h-3 bg-gray-300 dark:bg-gray-700 rounded-full overflow-hidden">
<div class="h-full bg-sitiweb-green transition-all duration-300"
style="width: {{ $skill->rating * 10 }}%">
</div>
</div>
</div>
@if ($skill->description)
<div class="text-gray-800 dark:text-gray-100 prose dark:prose-invert max-w-prose text-sm">
{!! nl2br(e($skill->description)) !!}
</div>
@endif
</div>
@endforeach
@endif
@if (isset($skills['other']))
<div class="flex flex-wrap gap-4 mt-8">
@foreach ($skills['other'] as $skill)
<div class="flex flex-col items-center w-16">
@if ($skill->getFirstMediaUrl('image'))
<img src="{{ $skill->getFirstMediaUrl('image') }}" alt="{{ $skill->title }}"
class="w-10 h-10 object-contain mb-1 hover:scale-125 transition hover:shadow-lg" >
@endif
<span class="text-xs text-center text-gray-700 dark:text-gray-300">{{ $skill->title }}</span>
</div>
@endforeach
</div>
@endif

View File

@@ -1,8 +1,16 @@
@foreach ($experience as $item) @foreach ($experience as $item)
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow space-y-2"> <hr role="presentation" class="w-full border-t border-zinc-950/10 dark:border-white/10 my-8">
<div class=" space-y-2">
<div class="flex items-center gap-4">
@if ($item->image())
<img src="{{ $item->imageUrl() }}" alt="{{ $item->title }}"
class="w-12 h-12 rounded-md shadow-md object-contain bg-white dark:bg-gray-700 p-1">
@endif
<h3 class="text-xl font-bold text-gray-900 dark:text-white"> <h3 class="text-xl font-bold text-gray-900 dark:text-white">
{{ $item->functie }} <span class="text-gray-500 dark:text-gray-400 font-normal">bij {{ $item->werkgever }}</span> {{ $item->functie }} <span class="text-gray-500 dark:text-gray-400 font-normal">bij
{{ $item->werkgever }}</span>
</h3> </h3>
</div>
<p class="text-sm text-gray-600 dark:text-gray-300 italic"> <p class="text-sm text-gray-600 dark:text-gray-300 italic">
{{ \Carbon\Carbon::parse($item->startdatum)->translatedFormat('F Y') }} {{ \Carbon\Carbon::parse($item->startdatum)->translatedFormat('F Y') }}

View File

@@ -43,6 +43,7 @@
</div> </div>
</main> </main>
</div> </div>
@stack('scripts')
</body> </body>
</html> </html>

View File

@@ -39,7 +39,7 @@
</p> </p>
@if (session('status') === 'verification-link-sent') @if (session('status') === 'verification-link-sent')
<p class="mt-2 font-medium text-sm text-green-600 dark:text-green-400"> <p class="mt-2 font-medium text-sm text-green-600 dark:text-sitiweb-green">
{{ __('A new verification link has been sent to your email address.') }} {{ __('A new verification link has been sent to your email address.') }}
</p> </p>
@endif @endif

View File

@@ -1,24 +1,57 @@
<div class="space-y-6"> <div class="space-y-6">
<div>
<label for="type" class="block text-sm font-medium text-gray-700 dark:text-gray-200">Type</label>
<select id="type" name="type" class="mt-1 w-full px-3 py-2 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded">
<option value="rating" @selected(old('type', $skill->type ?? 'rating') === 'rating')>Rating</option>
<option value="tag" @selected(old('type', $skill->type ?? '') === 'tag')>Tag</option>
<option value="other" @selected(old('type', $skill->type ?? '') === 'other')>Overig</option>
</select>
</div>
<div> <div>
<label for="title" class="block text-sm font-medium text-gray-700 dark:text-gray-200">Titel</label> <label for="title" class="block text-sm font-medium text-gray-700 dark:text-gray-200">Titel</label>
<input type="text" id="title" name="title" value="{{ old('title', $skill->title ?? '') }}" class="mt-1 w-full px-3 py-2 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded"> <input type="text" id="title" name="title" value="{{ old('title', $skill->title ?? '') }}" class="mt-1 w-full px-3 py-2 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded">
</div> </div>
<div> <div id="description-field">
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-200">Beschrijving</label> <label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-200">Beschrijving</label>
<textarea id="description" name="description" rows="4" class="mt-1 w-full px-3 py-2 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded">{{ old('description', $skill->description ?? '') }}</textarea> <textarea id="description" name="description" rows="4" class="mt-1 w-full px-3 py-2 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded">{{ old('description', $skill->description ?? '') }}</textarea>
</div> </div>
<div> <div id="rating-field">
<label for="rating" class="block text-sm font-medium text-gray-700 dark:text-gray-200">Beoordeling (110)</label> <label for="rating" class="block text-sm font-medium text-gray-700 dark:text-gray-200">Beoordeling (110)</label>
<input type="number" id="rating" name="rating" min="1" max="10" value="{{ old('rating', $skill->rating ?? 5) }}" class="mt-1 w-full px-3 py-2 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded"> <input type="number" id="rating" name="rating" min="1" max="10" value="{{ old('rating', $skill->rating ?? 5) }}" class="mt-1 w-full px-3 py-2 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded">
</div> </div>
<div> <div id="image-field">
<label for="image" class="block text-sm font-medium text-gray-700 dark:text-gray-200">Afbeelding</label> <label for="image" class="block text-sm font-medium text-gray-700 dark:text-gray-200">Afbeelding</label>
<input type="file" id="image" name="image" class="mt-1 text-gray-800 dark:text-gray-100"> <input type="file" id="image" name="image" class="mt-1 text-gray-800 dark:text-gray-100">
@if (!empty($skill) && $skill->getFirstMediaUrl('image')) @if (!empty($skill) && $skill->getFirstMediaUrl('image'))
<img src="{{ $skill->getFirstMediaUrl('image') }}" class="mt-2 max-w-xs rounded"> <img src="{{ $skill->getFirstMediaUrl('image') }}" class="mt-2 max-w-xs rounded">
@endif @endif
</div> </div>
</div> </div>
@push('scripts')
<script>
function toggleFields() {
const type = document.getElementById('type').value;
// Rating alleen tonen bij type = rating
document.getElementById('rating-field').style.display = (type === 'rating') ? 'block' : 'none';
// Image tonen bij rating en other
document.getElementById('image-field').style.display = (type === 'rating' || type === 'other') ? 'block' : 'none';
// Beschrijving alleen tonen bij rating
document.getElementById('description-field').style.display = (type === 'rating') ? 'block' : 'none';
}
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('type').addEventListener('change', toggleFields);
toggleFields(); // initial
});
</script>
@endpush

View File

@@ -1,27 +1,51 @@
<x-app-layout> <x-app-layout>
<div class="p-6"> <div class="p-6 space-y-10">
<h2 class="text-2xl font-semibold text-gray-800 dark:text-white mb-4">Vaardigheden</h2> <h2 class="text-2xl font-semibold text-gray-800 dark:text-white">Vaardigheden</h2>
<a href="{{ route('skills.create') }}" class="mb-4 inline-block px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Nieuwe vaardigheid</a>
<a href="{{ route('skills.create') }}" class="inline-block px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
Nieuwe vaardigheid
</a>
@php
$groupedSkills = $skills->groupBy('type');
@endphp
{{-- Rating --}}
@if($groupedSkills->has('rating'))
<div>
<h3 class="text-xl font-bold text-gray-700 dark:text-gray-200 mb-4">Ratings</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
@foreach ($skills as $skill) @foreach ($groupedSkills['rating'] as $skill)
<div class="p-4 bg-white dark:bg-gray-800 rounded shadow"> @include('frontend._card', ['skill' => $skill])
<h3 class="text-lg font-bold text-gray-800 dark:text-white">{{ $skill->title }}</h3>
<p class="text-sm text-gray-600 dark:text-gray-300 mb-2">Beoordeling: {{ $skill->rating }}/10</p>
<p class="text-sm text-gray-600 dark:text-gray-300">{{ $skill->description }}</p>
@if ($skill->getFirstMediaUrl('image'))
<img src="{{ $skill->getFirstMediaUrl('image') }}" alt="{{ $skill->title }}" class="mt-2 max-w-full h-32 object-contain rounded">
@endif
<div class="mt-4 flex justify-between">
<a href="{{ route('skills.edit', $skill) }}" class="text-blue-600 hover:underline">Bewerken</a>
<form action="{{ route('skills.destroy', $skill) }}" method="POST" onsubmit="return confirm('Weet je zeker dat je dit wilt verwijderen?')">
@csrf
@method('DELETE')
<button class="text-red-600 hover:underline">Verwijderen</button>
</form>
</div>
</div>
@endforeach @endforeach
</div> </div>
</div> </div>
@endif
{{-- Tags --}}
@if($groupedSkills->has('tag'))
<div>
<h3 class="text-xl font-bold text-gray-700 dark:text-gray-200 mb-4">Tags</h3>
<div class="flex flex-wrap gap-2">
@foreach ($groupedSkills['tag'] as $skill)
<span class="inline-block bg-blue-100 dark:bg-blue-800 text-blue-800 dark:text-blue-100 text-sm px-3 py-1 rounded">
{{ $skill->title }}
</span>
@endforeach
</div>
</div>
@endif
{{-- Overig --}}
@if($groupedSkills->has('other'))
<div>
<h3 class="text-xl font-bold text-gray-700 dark:text-gray-200 mb-4">Overige vaardigheden</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
@foreach ($groupedSkills['other'] as $skill)
@include('frontend._card', ['skill' => $skill])
@endforeach
</div>
</div>
@endif
</div>
</x-app-layout> </x-app-layout>

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,6 @@
<div class="space-y-6"> <div class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div> <div>
<label for="werkgever" class="block text-sm font-medium text-gray-700 dark:text-gray-200">Werkgever</label> <label for="werkgever" class="block text-sm font-medium text-gray-700 dark:text-gray-200">Werkgever</label>
@@ -32,6 +34,7 @@
</div> </div>
<div> <div>
<label for="beschrijving" class="block text-sm font-medium text-gray-700 dark:text-gray-200">Beschrijving</label> <label for="beschrijving" class="block text-sm font-medium text-gray-700 dark:text-gray-200">Beschrijving</label>
<textarea id="beschrijving" name="beschrijving" rows="4" class="mt-1 w-full px-3 py-2 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded">{{ old('beschrijving', $workExperience->beschrijving ?? '') }}</textarea> <textarea id="beschrijving" name="beschrijving" rows="4" class="mt-1 w-full px-3 py-2 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded">{{ old('beschrijving', $workExperience->beschrijving ?? '') }}</textarea>
</div> </div>
@@ -39,8 +42,9 @@
<div> <div>
<label for="afbeelding" class="block text-sm font-medium text-gray-700 dark:text-gray-200">Afbeelding</label> <label for="afbeelding" class="block text-sm font-medium text-gray-700 dark:text-gray-200">Afbeelding</label>
<input type="file" id="afbeelding" name="afbeelding" class="mt-1 text-gray-800 dark:text-gray-100"> <input type="file" id="afbeelding" name="afbeelding" class="mt-1 text-gray-800 dark:text-gray-100">
@if (!empty($workExperience) && $workExperience->getFirstMediaUrl('afbeelding'))
<img src="{{ $workExperience->getFirstMediaUrl('afbeelding') }}" class="mt-2 max-w-xs rounded"> @if (!empty($workExperience) && $workExperience->image())
<img src="{{ $workExperience->imageUrl() }}" class="mt-2 max-w-xs rounded">
@endif @endif
</div> </div>
</div> </div>

View File

@@ -7,7 +7,7 @@
</div> </div>
@if (session('success')) @if (session('success'))
<div class="mb-4 text-green-700 dark:text-green-400">{{ session('success') }}</div> <div class="mb-4 text-green-700 dark:text-sitiweb-green">{{ session('success') }}</div>
@endif @endif
<div class="space-y-4"> <div class="space-y-4">
@@ -20,7 +20,7 @@
<p class="mt-2 text-gray-700 dark:text-gray-200">{{ $experience->beschrijving }}</p> <p class="mt-2 text-gray-700 dark:text-gray-200">{{ $experience->beschrijving }}</p>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<a href="{{ route('work-experiences.edit', $experience) }}" class="text-blue-500 hover:underline">Bewerken</a> <a href="{{ route('work-experiences.edit', $experience) }}" class="text-sitiweb-green hover:underline">Bewerken</a>
<form method="POST" action="{{ route('work-experiences.destroy', $experience) }}"> <form method="POST" action="{{ route('work-experiences.destroy', $experience) }}">
@csrf @csrf
@method('DELETE') @method('DELETE')
@@ -28,8 +28,8 @@
</form> </form>
</div> </div>
</div> </div>
@if ($experience->getFirstMediaUrl('afbeelding')) @if ($experience->image())
<img src="{{ $experience->getFirstMediaUrl('afbeelding') }}" class="mt-4 max-w-xs rounded"> <img src="{{ $experience->imageUrl() }}" class="mt-4 max-w-xs rounded">
@endif @endif
</div> </div>
@endforeach @endforeach

View File

@@ -11,12 +11,8 @@ Route::get('/', [FrontendController::class, 'index'])->name('home');
Route::get('/dashboard', function () { Route::get('/dashboard', function () {
return view('dashboard'); return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard'); })->middleware(['auth', 'verified'])->name('dashboard');
Route::get('/getPersonalia/{id}', function ($id) { Route::get('/getPersonalia/{id}', [FrontendController::class, 'getPersonalia'])->name('personalia');
$item = \App\Models\Personalia::findOrFail($id); Route::post('/contact', [FrontendController::class, 'message'])->name('contact');
return response()->json([
'value' => $item->value,
]);
});
Route::middleware('auth')->group(function () { Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');

View File

@@ -1,4 +1,46 @@
* # Alles negeren
!private/ /public/*
!public/
!.gitignore # Uitzonderingen (wél toevoegen aan Git)
!public/1/
!public/2/
!public/3/
!public/4/
!public/5/
!public/6/
!public/7/
!public/8/
!public/9/
!public/10/
!public/11/
!public/12/
!public/13/
!public/14/
!public/15/
!public/16/
!public/17/
!public/18/
!public/19/
!public/20/
!public/21/
!public/22/
!public/23/
!public/24/
!public/25/
!public/26/
!public/27/
!public/28/
!public/29/
!public/30/
!public/31/
!public/32/
!public/33/
!public/34/
!public/35/
!public/36/
!public/37/
!public/38/
# Specifieke losse bestanden
!public/roberto.png
!public/sitiweb.svg

View File

@@ -1,2 +0,0 @@
*
!.gitignore

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
storage/app/public/8/hr.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" id="svg2" xml:space="preserve" viewBox="0 0 406.66666 473.33334" sodipodi:docname="Beeldmerk.ai"><metadata id="metadata8"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/></cc:Work></rdf:RDF></metadata><defs id="defs6"/><sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="640" inkscape:window-height="480" id="namedview4"/><g id="g10" inkscape:groupmode="layer" inkscape:label="Beeldmerk" transform="matrix(1.3333333,0,0,-1.3333333,0,473.33333)"><g id="g12" transform="translate(192.9405,297.8561)"><path d="M 0,0 3.661,-204.853 95.917,-260.941 95.291,47.32 Z" style="fill:#009000;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path14"/></g><g id="g16" transform="translate(287.7484,238.091)"><path d="M 0,0 -183.898,-87.583 -275.154,-31.495 1.239,108.114 Z" style="fill:#00ab00;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path18"/></g><g id="g20" transform="translate(172.7637,52.3648)"><path d="M 0,0 -93.429,-43.886 -96.399,59 -2.97,102.886 Z" style="fill:#ee0004;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path22"/></g><g id="g24" transform="translate(169.794,155.2513)"><path d="m 0,0 2.97,-102.886 -93.429,-43.887" style="fill:#d8000e;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path26"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -14,6 +14,9 @@ export default {
fontFamily: { fontFamily: {
sans: ['Figtree', ...defaultTheme.fontFamily.sans], sans: ['Figtree', ...defaultTheme.fontFamily.sans],
}, },
colors: {
'sitiweb-green': '#00AB00',
},
}, },
}, },