This commit is contained in:
2025-10-07 18:35:31 +02:00
parent 1bc3056c77
commit 5c98259148
21 changed files with 1237 additions and 99 deletions
@@ -0,0 +1,196 @@
<?php
namespace App\Http\Controllers;
use App\Models\PageContent;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Validation\ValidationException;
class PageContentController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(): JsonResponse
{
try {
$content = PageContent::all();
return response()->json($content);
} catch (\Exception $e) {
return response()->json([
'message' => 'Pagina-inhoud ophalen mislukt',
'error' => $e->getMessage()
], 500);
}
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request): JsonResponse
{
try {
$validated = $request->validate([
'key' => 'required|string|max:255|unique:page_content,key',
'value' => 'required|string',
'description' => 'nullable|string',
], [
'key.required' => 'Sleutel is verplicht',
'key.unique' => 'Deze sleutel bestaat al',
'key.max' => 'Sleutel mag maximaal 255 karakters bevatten',
'value.required' => 'Waarde is verplicht',
]);
$content = PageContent::create($validated);
return response()->json($content, 201);
} catch (ValidationException $e) {
return response()->json([
'message' => 'Validatie mislukt',
'errors' => $e->errors()
], 422);
} catch (\Exception $e) {
return response()->json([
'message' => 'Pagina-inhoud aanmaken mislukt',
'error' => $e->getMessage()
], 500);
}
}
/**
* Display the specified resource.
*/
public function show(PageContent $pageContent): JsonResponse
{
try {
return response()->json($pageContent);
} catch (\Exception $e) {
return response()->json([
'message' => 'Pagina-inhoud ophalen mislukt',
'error' => $e->getMessage()
], 500);
}
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, PageContent $pageContent): JsonResponse
{
try {
$validated = $request->validate([
'value' => 'required|string',
'description' => 'nullable|string',
], [
'value.required' => 'Waarde is verplicht',
]);
$pageContent->update($validated);
return response()->json($pageContent);
} catch (ValidationException $e) {
return response()->json([
'message' => 'Validatie mislukt',
'errors' => $e->errors()
], 422);
} catch (\Exception $e) {
return response()->json([
'message' => 'Pagina-inhoud bijwerken mislukt',
'error' => $e->getMessage()
], 500);
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy(PageContent $pageContent): JsonResponse
{
try {
$pageContent->delete();
return response()->json([
'message' => 'Pagina-inhoud succesvol verwijderd'
]);
} catch (\Exception $e) {
return response()->json([
'message' => 'Pagina-inhoud verwijderen mislukt',
'error' => $e->getMessage()
], 500);
}
}
/**
* Get a specific content by key
*/
public function getByKey(string $key): JsonResponse
{
try {
$content = PageContent::where('key', $key)->first();
if (!$content) {
return response()->json([
'message' => 'Pagina-inhoud niet gevonden'
], 404);
}
return response()->json($content);
} catch (\Exception $e) {
return response()->json([
'message' => 'Pagina-inhoud ophalen mislukt',
'error' => $e->getMessage()
], 500);
}
}
/**
* Update or create content by key
*/
public function updateByKey(Request $request, string $key): JsonResponse
{
try {
$validated = $request->validate([
'value' => 'required|string',
'description' => 'nullable|string',
], [
'value.required' => 'Waarde is verplicht',
]);
$content = PageContent::updateOrCreate(
['key' => $key],
[
'value' => $validated['value'],
'description' => $validated['description'] ?? null
]
);
return response()->json($content);
} catch (ValidationException $e) {
return response()->json([
'message' => 'Validatie mislukt',
'errors' => $e->errors()
], 422);
} catch (\Exception $e) {
return response()->json([
'message' => 'Pagina-inhoud bijwerken mislukt',
'error' => $e->getMessage()
], 500);
}
}
/**
* Get all content as key-value pairs for public access
*/
public function getPublicContent(): JsonResponse
{
try {
$content = PageContent::all()->pluck('value', 'key');
return response()->json($content);
} catch (\Exception $e) {
return response()->json([
'message' => 'Pagina-inhoud ophalen mislukt',
'error' => $e->getMessage()
], 500);
}
}
}
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use App\Models\ServiceRequest;
use App\Models\Setting;
use App\Models\Communication;
use App\Mail\ServiceRequestNotification;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Validation\ValidationException;
@@ -277,10 +278,11 @@ class ServiceRequestController extends Controller
{
try {
$adminEmail = Setting::getValue('admin_notification_email');
if ($adminEmail) {
// TODO: Implement actual email sending logic
// For now, we'll just log it
// Send email to admin
Mail::to($adminEmail)->send(new ServiceRequestNotification($serviceRequest));
Log::info('Notificatie e-mail verzonden naar beheerder', [
'service_request_id' => $serviceRequest->id,
'admin_email' => $adminEmail,
@@ -0,0 +1,55 @@
<?php
namespace App\Mail;
use App\Models\ServiceRequest;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class ServiceRequestNotification extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*/
public function __construct(
public ServiceRequest $serviceRequest
) {
//
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Nieuwe Service Aanvraag - ' . $this->serviceRequest->name,
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
view: 'emails.service-request-notification',
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PageContent extends Model
{
use HasFactory;
protected $table = 'page_content';
protected $fillable = [
'key',
'value',
'description',
];
protected $casts = [
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
}
@@ -0,0 +1,150 @@
<?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('page_content', function (Blueprint $table) {
$table->id();
$table->string('key')->unique();
$table->text('value');
$table->string('description')->nullable();
$table->timestamps();
});
// Insert default content
DB::table('page_content')->insert([
// Home page - Hero section
[
'key' => 'home_hero_title',
'value' => '🚲 Welkom bij Fiets Service Centrum',
'description' => 'Home page hero title',
'created_at' => now(),
'updated_at' => now(),
],
[
'key' => 'home_hero_subtitle',
'value' => 'Professionele fietsreparatie en onderhoudsdiensten',
'description' => 'Home page hero subtitle',
'created_at' => now(),
'updated_at' => now(),
],
// Home page - Feature panels
[
'key' => 'home_feature_1_title',
'value' => '🔧 Deskundige Reparaties',
'description' => 'Home page feature 1 title',
'created_at' => now(),
'updated_at' => now(),
],
[
'key' => 'home_feature_1_text',
'value' => 'Professionele fietsmonteurs met jarenlange ervaring',
'description' => 'Home page feature 1 caption',
'created_at' => now(),
'updated_at' => now(),
],
[
'key' => 'home_feature_2_title',
'value' => '⚡ Snelle Service',
'description' => 'Home page feature 2 title',
'created_at' => now(),
'updated_at' => now(),
],
[
'key' => 'home_feature_2_text',
'value' => 'Snelle doorlooptijden voor de meeste reparaties',
'description' => 'Home page feature 2 caption',
'created_at' => now(),
'updated_at' => now(),
],
[
'key' => 'home_feature_3_title',
'value' => '💰 Eerlijke Prijzen',
'description' => 'Home page feature 3 title',
'created_at' => now(),
'updated_at' => now(),
],
[
'key' => 'home_feature_3_text',
'value' => 'Concurrerende tarieven zonder verborgen kosten',
'description' => 'Home page feature 3 caption',
'created_at' => now(),
'updated_at' => now(),
],
[
'key' => 'home_feature_4_title',
'value' => '📅 Eenvoudig Boeken',
'description' => 'Home page feature 4 title',
'created_at' => now(),
'updated_at' => now(),
],
[
'key' => 'home_feature_4_text',
'value' => 'Eenvoudig online boekingssysteem',
'description' => 'Home page feature 4 caption',
'created_at' => now(),
'updated_at' => now(),
],
// Home page - CTA section
[
'key' => 'home_cta_title',
'value' => 'Klaar om je fiets te laten repareren?',
'description' => 'Home page CTA title',
'created_at' => now(),
'updated_at' => now(),
],
[
'key' => 'home_cta_subtitle',
'value' => 'Boek je afspraak online en krijg je fiets weer in topconditie.',
'description' => 'Home page CTA subtitle',
'created_at' => now(),
'updated_at' => now(),
],
[
'key' => 'home_cta_button_1_text',
'value' => 'Nu Service Boeken',
'description' => 'Home page CTA button 1 text',
'created_at' => now(),
'updated_at' => now(),
],
[
'key' => 'home_cta_button_2_text',
'value' => '💰 Prijslijst Bekijken',
'description' => 'Home page CTA button 2 text',
'created_at' => now(),
'updated_at' => now(),
],
// Booking page
[
'key' => 'booking_header_title',
'value' => '🚲 Boek Je Fiets Service',
'description' => 'Booking page header title',
'created_at' => now(),
'updated_at' => now(),
],
[
'key' => 'booking_header_subtitle',
'value' => 'Vul het onderstaande formulier in om je fietsreparatie of onderhoud in te plannen',
'description' => 'Booking page header subtitle',
'created_at' => now(),
'updated_at' => now(),
],
]);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('page_content');
}
};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,45 @@
<svg viewBox="0 0 600 250" xmlns="http://www.w3.org/2000/svg">
<!-- Main Logo Group -->
<g transform="translate(300, 125)">
<!-- Bike Icon -->
<g transform="translate(-180, -30)">
<!-- Rear wheel -->
<circle cx="40" cy="45" r="30" fill="none" stroke="#ffffff" stroke-width="5"/>
<circle cx="40" cy="45" r="20" fill="none" stroke="#ffffff" stroke-width="2" opacity="0.3"/>
<circle cx="40" cy="45" r="3" fill="#ffffff"/>
<!-- Front wheel -->
<circle cx="160" cy="45" r="30" fill="none" stroke="#ffffff" stroke-width="5"/>
<circle cx="160" cy="45" r="20" fill="none" stroke="#ffffff" stroke-width="2" opacity="0.3"/>
<circle cx="160" cy="45" r="3" fill="#ffffff"/>
<!-- Frame -->
<path d="M 40 45 L 100 45 L 130 5 M 100 45 L 160 45"
fill="none" stroke="#ffffff" stroke-width="5"
stroke-linejoin="round" stroke-linecap="round"/>
<!-- Seat post and seat -->
<path d="M 70 45 L 90 10" fill="none" stroke="#ffffff" stroke-width="5" stroke-linecap="round"/>
<ellipse cx="90" cy="5" rx="12" ry="5" fill="#f0d9b5"/>
<!-- Handlebars -->
<path d="M 130 5 L 145 5" fill="none" stroke="#ffffff" stroke-width="5" stroke-linecap="round"/>
<circle cx="128" cy="5" r="4" fill="#ffffff"/>
<!-- Pedal area -->
<circle cx="100" cy="45" r="8" fill="#f0d9b5"/>
<path d="M 95 50 L 105 50" fill="none" stroke="#ffffff" stroke-width="3" stroke-linecap="round"/>
</g>
<!-- Text -->
<text x="30" y="0" font-family="Arial, Helvetica, sans-serif" font-size="52" font-weight="bold" fill="#ffffff" letter-spacing="3">BIKE</text>
<text x="30" y="50" font-family="Arial, Helvetica, sans-serif" font-size="48" font-weight="600" fill="#f0d9b5" letter-spacing="2">SERVICE</text>
<!-- Accent lines -->
<line x1="-185" y1="68" x2="250" y2="68" stroke="#ffffff" stroke-width="3"/>
<line x1="-185" y1="72" x2="250" y2="72" stroke="#f0d9b5" stroke-width="1.5"/>
<!-- Tagline -->
<text x="32" y="100" font-family="Arial, Helvetica, sans-serif" font-size="16" font-style="italic" fill="#f0f0f0" letter-spacing="0.5">Jouw fiets, onze zorg</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,48 @@
<svg viewBox="0 0 600 250" xmlns="http://www.w3.org/2000/svg">
<!-- White background for clean export -->
<rect width="600" height="250" fill="white"/>
<!-- Main Logo Group -->
<g transform="translate(300, 125)">
<!-- Bike Icon -->
<g transform="translate(-180, -30)">
<!-- Rear wheel -->
<circle cx="40" cy="45" r="30" fill="none" stroke="#2c5f5d" stroke-width="5"/>
<circle cx="40" cy="45" r="20" fill="none" stroke="#2c5f5d" stroke-width="2" opacity="0.3"/>
<circle cx="40" cy="45" r="3" fill="#2c5f5d"/>
<!-- Front wheel -->
<circle cx="160" cy="45" r="30" fill="none" stroke="#2c5f5d" stroke-width="5"/>
<circle cx="160" cy="45" r="20" fill="none" stroke="#2c5f5d" stroke-width="2" opacity="0.3"/>
<circle cx="160" cy="45" r="3" fill="#2c5f5d"/>
<!-- Frame -->
<path d="M 40 45 L 100 45 L 130 5 M 100 45 L 160 45"
fill="none" stroke="#2c5f5d" stroke-width="5"
stroke-linejoin="round" stroke-linecap="round"/>
<!-- Seat post and seat -->
<path d="M 70 45 L 90 10" fill="none" stroke="#2c5f5d" stroke-width="5" stroke-linecap="round"/>
<ellipse cx="90" cy="5" rx="12" ry="5" fill="#8b6f47"/>
<!-- Handlebars -->
<path d="M 130 5 L 145 5" fill="none" stroke="#2c5f5d" stroke-width="5" stroke-linecap="round"/>
<circle cx="128" cy="5" r="4" fill="#2c5f5d"/>
<!-- Pedal area -->
<circle cx="100" cy="45" r="8" fill="#8b6f47"/>
<path d="M 95 50 L 105 50" fill="none" stroke="#2c5f5d" stroke-width="3" stroke-linecap="round"/>
</g>
<!-- Text -->
<text x="30" y="0" font-family="Arial, Helvetica, sans-serif" font-size="52" font-weight="bold" fill="#2c5f5d" letter-spacing="3">BIKE</text>
<text x="30" y="50" font-family="Arial, Helvetica, sans-serif" font-size="48" font-weight="600" fill="#8b6f47" letter-spacing="2">SERVICE</text>
<!-- Accent lines -->
<line x1="-185" y1="68" x2="250" y2="68" stroke="#2c5f5d" stroke-width="3"/>
<line x1="-185" y1="72" x2="250" y2="72" stroke="#8b6f47" stroke-width="1.5"/>
<!-- Tagline -->
<text x="32" y="100" font-family="Arial, Helvetica, sans-serif" font-size="16" font-style="italic" fill="#666666" letter-spacing="0.5">Jouw fiets, onze zorg</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

+15
View File
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/images/bikeservice_logo_primary.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Professionele fietsreparatie en onderhoudsdiensten" />
<title>Bike Service - Jouw fiets, onze zorg</title>
<script type="module" crossorigin src="/assets/index-_eFKiDPi.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Dg5tUgMl.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
@@ -0,0 +1,115 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nieuwe Service Aanvraag</title>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
line-height: 1.6;
color: #333;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.header {
background: linear-gradient(135deg, #2c5f5d 0%, #3d7a77 100%);
color: white;
padding: 20px;
border-radius: 8px 8px 0 0;
text-align: center;
}
.content {
background: #f8f9fa;
padding: 30px;
border-radius: 0 0 8px 8px;
}
.info-row {
margin-bottom: 15px;
padding: 10px;
background: white;
border-radius: 5px;
}
.label {
font-weight: bold;
color: #2c5f5d;
margin-bottom: 5px;
}
.value {
color: #666;
}
.footer {
text-align: center;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #ddd;
color: #999;
font-size: 12px;
}
</style>
</head>
<body>
<div class="header">
<h1>🚲 Nieuwe Service Aanvraag</h1>
</div>
<div class="content">
<p>Er is een nieuwe service aanvraag ontvangen via de website:</p>
<div class="info-row">
<div class="label">Naam:</div>
<div class="value">{{ $serviceRequest->name }}</div>
</div>
<div class="info-row">
<div class="label">Adres:</div>
<div class="value">{{ $serviceRequest->address }}, {{ $serviceRequest->place }}</div>
</div>
@if($serviceRequest->email)
<div class="info-row">
<div class="label">E-mail:</div>
<div class="value">{{ $serviceRequest->email }}</div>
</div>
@endif
@if($serviceRequest->phone)
<div class="info-row">
<div class="label">Telefoon:</div>
<div class="value">{{ $serviceRequest->phone }}</div>
</div>
@endif
<div class="info-row">
<div class="label">Gewenste datum:</div>
<div class="value">{{ \Carbon\Carbon::parse($serviceRequest->requested_date)->format('d-m-Y') }} om {{ $serviceRequest->requested_time }}</div>
</div>
<div class="info-row">
<div class="label">Reparatie beschrijving:</div>
<div class="value">{{ $serviceRequest->repair_description }}</div>
</div>
<div class="info-row">
<div class="label">Status:</div>
<div class="value">{{ ucfirst($serviceRequest->status) }}</div>
</div>
<div class="info-row">
<div class="label">Aangemaakt op:</div>
<div class="value">{{ $serviceRequest->created_at->format('d-m-Y H:i') }}</div>
</div>
<p style="margin-top: 20px;">
<a href="{{ config('app.url') }}/admin" style="background-color: #2c5f5d; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; display: inline-block;">
Bekijk in Admin Paneel
</a>
</p>
</div>
<div class="footer">
<p>Dit is een automatisch gegenereerde e-mail van uw Fiets Service systeem.</p>
</div>
</body>
</html>
+9
View File
@@ -7,6 +7,7 @@ use App\Http\Controllers\BlockedDateController;
use App\Http\Controllers\SettingController;
use App\Http\Controllers\AuthController;
use App\Http\Controllers\PricelistController;
use App\Http\Controllers\PageContentController;
/*
|--------------------------------------------------------------------------
@@ -31,6 +32,9 @@ Route::get('/public/blocked-dates', [BlockedDateController::class, 'publicIndex'
// Public service request creation (customers can submit requests)
Route::post('/public/service-requests', [ServiceRequestController::class, 'store']);
// Public page content (customers can view page text)
Route::get('/public/page-content', [PageContentController::class, 'getPublicContent']);
// Protected routes (require authentication)
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', function (Request $request) {
@@ -56,4 +60,9 @@ Route::middleware('auth:sanctum')->group(function () {
// Admin pricelist management
Route::get('/admin/pricelist', [PricelistController::class, 'adminIndex']);
Route::apiResource('pricelist', PricelistController::class)->except(['index']);
// Admin page content management
Route::apiResource('page-content', PageContentController::class);
Route::get('page-content/key/{key}', [PageContentController::class, 'getByKey']);
Route::put('page-content/key/{key}', [PageContentController::class, 'updateByKey']);
});
+2 -2
View File
@@ -1,9 +1,9 @@
# Deployment Guide for Bike Service Application
This guide will help you deploy your Laravel + Vue.js application to an Ubuntu server.
## Prerequisites
wwthe
- Ubuntu 20.04+ server with root/sudo access
- Domain name (optional but recommended)
- SSH access to your server
+4 -3
View File
@@ -1,10 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<html lang="nl">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/svg+xml" href="/images/bikeservice_logo_primary.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue + Laravel App</title>
<meta name="description" content="Professionele fietsreparatie en onderhoudsdiensten" />
<title>Bike Service - Jouw fiets, onze zorg</title>
</head>
<body>
<div id="app"></div>
@@ -0,0 +1,45 @@
<svg viewBox="0 0 600 250" xmlns="http://www.w3.org/2000/svg">
<!-- Main Logo Group -->
<g transform="translate(300, 125)">
<!-- Bike Icon -->
<g transform="translate(-180, -30)">
<!-- Rear wheel -->
<circle cx="40" cy="45" r="30" fill="none" stroke="#ffffff" stroke-width="5"/>
<circle cx="40" cy="45" r="20" fill="none" stroke="#ffffff" stroke-width="2" opacity="0.3"/>
<circle cx="40" cy="45" r="3" fill="#ffffff"/>
<!-- Front wheel -->
<circle cx="160" cy="45" r="30" fill="none" stroke="#ffffff" stroke-width="5"/>
<circle cx="160" cy="45" r="20" fill="none" stroke="#ffffff" stroke-width="2" opacity="0.3"/>
<circle cx="160" cy="45" r="3" fill="#ffffff"/>
<!-- Frame -->
<path d="M 40 45 L 100 45 L 130 5 M 100 45 L 160 45"
fill="none" stroke="#ffffff" stroke-width="5"
stroke-linejoin="round" stroke-linecap="round"/>
<!-- Seat post and seat -->
<path d="M 70 45 L 90 10" fill="none" stroke="#ffffff" stroke-width="5" stroke-linecap="round"/>
<ellipse cx="90" cy="5" rx="12" ry="5" fill="#f0d9b5"/>
<!-- Handlebars -->
<path d="M 130 5 L 145 5" fill="none" stroke="#ffffff" stroke-width="5" stroke-linecap="round"/>
<circle cx="128" cy="5" r="4" fill="#ffffff"/>
<!-- Pedal area -->
<circle cx="100" cy="45" r="8" fill="#f0d9b5"/>
<path d="M 95 50 L 105 50" fill="none" stroke="#ffffff" stroke-width="3" stroke-linecap="round"/>
</g>
<!-- Text -->
<text x="30" y="0" font-family="Arial, Helvetica, sans-serif" font-size="52" font-weight="bold" fill="#ffffff" letter-spacing="3">BIKE</text>
<text x="30" y="50" font-family="Arial, Helvetica, sans-serif" font-size="48" font-weight="600" fill="#f0d9b5" letter-spacing="2">SERVICE</text>
<!-- Accent lines -->
<line x1="-185" y1="68" x2="250" y2="68" stroke="#ffffff" stroke-width="3"/>
<line x1="-185" y1="72" x2="250" y2="72" stroke="#f0d9b5" stroke-width="1.5"/>
<!-- Tagline -->
<text x="32" y="100" font-family="Arial, Helvetica, sans-serif" font-size="16" font-style="italic" fill="#f0f0f0" letter-spacing="0.5">Jouw fiets, onze zorg</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,48 @@
<svg viewBox="0 0 600 250" xmlns="http://www.w3.org/2000/svg">
<!-- White background for clean export -->
<rect width="600" height="250" fill="white"/>
<!-- Main Logo Group -->
<g transform="translate(300, 125)">
<!-- Bike Icon -->
<g transform="translate(-180, -30)">
<!-- Rear wheel -->
<circle cx="40" cy="45" r="30" fill="none" stroke="#2c5f5d" stroke-width="5"/>
<circle cx="40" cy="45" r="20" fill="none" stroke="#2c5f5d" stroke-width="2" opacity="0.3"/>
<circle cx="40" cy="45" r="3" fill="#2c5f5d"/>
<!-- Front wheel -->
<circle cx="160" cy="45" r="30" fill="none" stroke="#2c5f5d" stroke-width="5"/>
<circle cx="160" cy="45" r="20" fill="none" stroke="#2c5f5d" stroke-width="2" opacity="0.3"/>
<circle cx="160" cy="45" r="3" fill="#2c5f5d"/>
<!-- Frame -->
<path d="M 40 45 L 100 45 L 130 5 M 100 45 L 160 45"
fill="none" stroke="#2c5f5d" stroke-width="5"
stroke-linejoin="round" stroke-linecap="round"/>
<!-- Seat post and seat -->
<path d="M 70 45 L 90 10" fill="none" stroke="#2c5f5d" stroke-width="5" stroke-linecap="round"/>
<ellipse cx="90" cy="5" rx="12" ry="5" fill="#8b6f47"/>
<!-- Handlebars -->
<path d="M 130 5 L 145 5" fill="none" stroke="#2c5f5d" stroke-width="5" stroke-linecap="round"/>
<circle cx="128" cy="5" r="4" fill="#2c5f5d"/>
<!-- Pedal area -->
<circle cx="100" cy="45" r="8" fill="#8b6f47"/>
<path d="M 95 50 L 105 50" fill="none" stroke="#2c5f5d" stroke-width="3" stroke-linecap="round"/>
</g>
<!-- Text -->
<text x="30" y="0" font-family="Arial, Helvetica, sans-serif" font-size="52" font-weight="bold" fill="#2c5f5d" letter-spacing="3">BIKE</text>
<text x="30" y="50" font-family="Arial, Helvetica, sans-serif" font-size="48" font-weight="600" fill="#8b6f47" letter-spacing="2">SERVICE</text>
<!-- Accent lines -->
<line x1="-185" y1="68" x2="250" y2="68" stroke="#2c5f5d" stroke-width="3"/>
<line x1="-185" y1="72" x2="250" y2="72" stroke="#8b6f47" stroke-width="1.5"/>
<!-- Tagline -->
<text x="32" y="100" font-family="Arial, Helvetica, sans-serif" font-size="16" font-style="italic" fill="#666666" letter-spacing="0.5">Jouw fiets, onze zorg</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

+64 -11
View File
@@ -3,7 +3,7 @@
<nav class="navbar">
<div class="nav-container">
<router-link to="/" class="nav-logo">
🚲 Fiets Service
<img src="/images/bikeservice_logo_navbar.svg" alt="Bike Service Logo" class="logo-image" />
</router-link>
<div class="nav-menu">
<router-link to="/" class="nav-link">Home</router-link>
@@ -13,7 +13,7 @@
</div>
</div>
</nav>
<main class="main-content">
<router-view />
</main>
@@ -41,6 +41,19 @@ export default {
</script>
<style>
:root {
--primary-color: #2c5f5d;
--primary-dark: #1f4443;
--primary-light: #3d7a77;
--secondary-color: #8b6f47;
--secondary-dark: #6d5637;
--secondary-light: #a68a5f;
--text-dark: #2c3e50;
--text-gray: #666;
--bg-light: #f5f5f5;
--bg-white: #ffffff;
}
* {
margin: 0;
padding: 0;
@@ -49,13 +62,13 @@ export default {
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f5f5;
background-color: var(--bg-light);
}
.navbar {
background: linear-gradient(135deg, #2c3e50 0%, #3498db 100%);
padding: 1rem 0;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
padding: 0.5rem 0;
box-shadow: 0 2px 10px rgba(44, 95, 93, 0.2);
}
.nav-container {
@@ -68,15 +81,26 @@ body {
}
.nav-logo {
color: white;
display: flex;
align-items: center;
text-decoration: none;
font-size: 1.5rem;
font-weight: bold;
}
.logo-image {
height: 70px;
width: auto;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
transition: transform 0.3s ease;
}
.logo-image:hover {
transform: scale(1.05);
}
.nav-menu {
display: flex;
gap: 2rem;
align-items: center;
}
.nav-link {
@@ -84,11 +108,17 @@ body {
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 5px;
transition: background-color 0.3s ease;
transition: all 0.3s ease;
font-weight: 500;
}
.nav-link:hover {
background-color: rgba(255,255,255,0.1);
background-color: rgba(255, 255, 255, 0.15);
transform: translateY(-1px);
}
.nav-link.router-link-active {
background-color: var(--secondary-color);
}
.main-content {
@@ -96,4 +126,27 @@ body {
margin: 2rem auto;
padding: 0 2rem;
}
@media (max-width: 768px) {
.nav-container {
flex-direction: column;
gap: 1rem;
padding: 1rem;
}
.logo-image {
height: 75px;
}
.nav-menu {
flex-wrap: wrap;
justify-content: center;
gap: 1rem;
}
.nav-link {
padding: 0.4rem 0.8rem;
font-size: 0.9rem;
}
}
</style>
+270 -21
View File
@@ -135,18 +135,134 @@
</div>
</div>
<!-- Page Content Tab -->
<div v-if="activeTab === 'content'" class="tab-content">
<div class="section-header">
<h2>Pagina Teksten Beheer</h2>
</div>
<div class="content-sections">
<!-- Home Page Hero -->
<div class="content-section">
<h3>Openingspagina - Hero Sectie</h3>
<div class="content-form">
<div class="form-group">
<label>Hoofdtekst (Header)</label>
<input type="text" :value="getContentValue('home_hero_title')" @input="updateContentValue('home_hero_title', $event.target.value)" @blur="savePageContent('home_hero_title')" />
</div>
<div class="form-group">
<label>Subtekst (Subheader)</label>
<input type="text" :value="getContentValue('home_hero_subtitle')" @input="updateContentValue('home_hero_subtitle', $event.target.value)" @blur="savePageContent('home_hero_subtitle')" />
</div>
</div>
</div>
<!-- Home Page Features -->
<div class="content-section">
<h3>Openingspagina - Panelen</h3>
<div class="content-form">
<div class="feature-group">
<h4>Paneel 1</h4>
<div class="form-group">
<label>Koptekst</label>
<input type="text" :value="getContentValue('home_feature_1_title')" @input="updateContentValue('home_feature_1_title', $event.target.value)" @blur="savePageContent('home_feature_1_title')" />
</div>
<div class="form-group">
<label>Onderschrift</label>
<textarea :value="getContentValue('home_feature_1_text')" @input="updateContentValue('home_feature_1_text', $event.target.value)" @blur="savePageContent('home_feature_1_text')" rows="2"></textarea>
</div>
</div>
<div class="feature-group">
<h4>Paneel 2</h4>
<div class="form-group">
<label>Koptekst</label>
<input type="text" :value="getContentValue('home_feature_2_title')" @input="updateContentValue('home_feature_2_title', $event.target.value)" @blur="savePageContent('home_feature_2_title')" />
</div>
<div class="form-group">
<label>Onderschrift</label>
<textarea :value="getContentValue('home_feature_2_text')" @input="updateContentValue('home_feature_2_text', $event.target.value)" @blur="savePageContent('home_feature_2_text')" rows="2"></textarea>
</div>
</div>
<div class="feature-group">
<h4>Paneel 3</h4>
<div class="form-group">
<label>Koptekst</label>
<input type="text" :value="getContentValue('home_feature_3_title')" @input="updateContentValue('home_feature_3_title', $event.target.value)" @blur="savePageContent('home_feature_3_title')" />
</div>
<div class="form-group">
<label>Onderschrift</label>
<textarea :value="getContentValue('home_feature_3_text')" @input="updateContentValue('home_feature_3_text', $event.target.value)" @blur="savePageContent('home_feature_3_text')" rows="2"></textarea>
</div>
</div>
<div class="feature-group">
<h4>Paneel 4</h4>
<div class="form-group">
<label>Koptekst</label>
<input type="text" :value="getContentValue('home_feature_4_title')" @input="updateContentValue('home_feature_4_title', $event.target.value)" @blur="savePageContent('home_feature_4_title')" />
</div>
<div class="form-group">
<label>Onderschrift</label>
<textarea :value="getContentValue('home_feature_4_text')" @input="updateContentValue('home_feature_4_text', $event.target.value)" @blur="savePageContent('home_feature_4_text')" rows="2"></textarea>
</div>
</div>
</div>
</div>
<!-- Home Page CTA -->
<div class="content-section">
<h3>Openingspagina - Knoppen Container</h3>
<div class="content-form">
<div class="form-group">
<label>Hoofdtekst</label>
<input type="text" :value="getContentValue('home_cta_title')" @input="updateContentValue('home_cta_title', $event.target.value)" @blur="savePageContent('home_cta_title')" />
</div>
<div class="form-group">
<label>Subtekst</label>
<textarea :value="getContentValue('home_cta_subtitle')" @input="updateContentValue('home_cta_subtitle', $event.target.value)" @blur="savePageContent('home_cta_subtitle')" rows="2"></textarea>
</div>
<div class="form-group">
<label>Knop 1 Tekst</label>
<input type="text" :value="getContentValue('home_cta_button_1_text')" @input="updateContentValue('home_cta_button_1_text', $event.target.value)" @blur="savePageContent('home_cta_button_1_text')" />
</div>
<div class="form-group">
<label>Knop 2 Tekst</label>
<input type="text" :value="getContentValue('home_cta_button_2_text')" @input="updateContentValue('home_cta_button_2_text', $event.target.value)" @blur="savePageContent('home_cta_button_2_text')" />
</div>
</div>
</div>
<!-- Booking Page -->
<div class="content-section">
<h3>Boekingspagina - Header</h3>
<div class="content-form">
<div class="form-group">
<label>Hoofdtekst (Header)</label>
<input type="text" :value="getContentValue('booking_header_title')" @input="updateContentValue('booking_header_title', $event.target.value)" @blur="savePageContent('booking_header_title')" />
</div>
<div class="form-group">
<label>Onderschrift</label>
<textarea :value="getContentValue('booking_header_subtitle')" @input="updateContentValue('booking_header_subtitle', $event.target.value)" @blur="savePageContent('booking_header_subtitle')" rows="2"></textarea>
</div>
</div>
</div>
</div>
</div>
<!-- Settings Tab -->
<div v-if="activeTab === 'settings'" class="tab-content">
<div class="section-header">
<h2>Applicatie Instellingen</h2>
</div>
<div class="settings-form">
<div class="setting-group">
<label for="admin_email">Beheer Notificatie E-mail</label>
<input
type="email"
id="admin_email"
<input
type="email"
id="admin_email"
v-model="settings.admin_notification_email"
placeholder="admin@voorbeeld.com"
/>
@@ -154,12 +270,12 @@
💾
</button>
</div>
<div class="setting-group">
<label for="business_name">Bedrijfsnaam</label>
<input
type="text"
id="business_name"
<input
type="text"
id="business_name"
v-model="settings.business_name"
placeholder="Fiets Service Centrum"
/>
@@ -381,6 +497,7 @@ export default {
const blockedDates = ref([])
const settings = ref({})
const pricelist = ref([])
const pageContent = ref([])
// Modal states
const showRequestModal = ref(false)
@@ -410,6 +527,7 @@ export default {
{ id: 'requests', label: 'Service Aanvragen' },
{ id: 'blocked', label: 'Geblokkeerde Data' },
{ id: 'pricelist', label: 'Prijslijst' },
{ id: 'content', label: 'Pagina Teksten' },
{ id: 'settings', label: 'Instellingen' }
]
@@ -516,6 +634,19 @@ export default {
console.error('Failed to fetch pricelist:', error)
}
}
const fetchPageContent = async () => {
try {
const response = await fetch('/api/page-content', {
headers: authStore.getAuthHeaders()
})
if (response.ok) {
pageContent.value = await response.json()
}
} catch (error) {
console.error('Failed to fetch page content:', error)
}
}
const refreshRequests = async () => {
loading.value = true
@@ -814,9 +945,9 @@ export default {
const deletePricelistItem = async (id) => {
if (!confirm('Weet je zeker dat je dit prijslijst item wilt verwijderen?')) return
try {
const response = await fetch(`/api/pricelist/${id}`, {
const response = await fetch(`/api/pricelist/${id}`, {
method: 'DELETE',
headers: authStore.getAuthHeaders()
})
@@ -827,6 +958,41 @@ export default {
console.error('Failed to delete pricelist item:', error)
}
}
// Page content functions
const getContentValue = (key) => {
const content = pageContent.value.find(c => c.key === key)
return content ? content.value : ''
}
const updateContentValue = (key, value) => {
const content = pageContent.value.find(c => c.key === key)
if (content) {
content.value = value
}
}
const savePageContent = async (key) => {
const content = pageContent.value.find(c => c.key === key)
if (!content) return
try {
const response = await fetch(`/api/page-content/key/${key}`, {
method: 'PUT',
headers: authStore.getAuthHeaders(),
body: JSON.stringify({
value: content.value,
description: content.description
})
})
if (response.ok) {
console.log(`Content ${key} updated successfully`)
}
} catch (error) {
console.error('Failed to update page content:', error)
}
}
// Utility functions
const formatDateTime = (date, time) => {
@@ -843,10 +1009,11 @@ export default {
onMounted(() => {
// Check authentication
authStore.checkAuth()
fetchRequests()
fetchBlockedDates()
fetchPricelist()
fetchPageContent()
fetchSettings()
})
@@ -858,6 +1025,7 @@ export default {
blockedDates,
settings,
pricelist,
pageContent,
user: computed(() => authStore.user),
showRequestModal,
showAddBlockedDate,
@@ -905,6 +1073,9 @@ export default {
updateRequestStatus,
updateRequestDate,
updateRequestTime,
getContentValue,
updateContentValue,
savePageContent,
formatDateTime,
formatDate
}
@@ -924,13 +1095,13 @@ export default {
}
.admin-header h1 {
color: #2c3e50;
color: var(--primary-color);
font-size: 2.5rem;
margin-bottom: 1rem;
}
.admin-header p {
color: #666;
color: var(--text-gray);
font-size: 1.2rem;
margin-bottom: 1rem;
}
@@ -984,8 +1155,8 @@ export default {
}
.tab-button.active {
border-bottom-color: #3498db;
color: #3498db;
border-bottom-color: var(--primary-color);
color: var(--primary-color);
font-weight: bold;
}
@@ -1008,12 +1179,12 @@ export default {
}
.section-header h2 {
color: #2c3e50;
color: var(--primary-color);
margin: 0;
}
.refresh-btn, .add-btn {
background: #3498db;
background: var(--primary-color);
color: white;
border: none;
width: 40px;
@@ -1029,9 +1200,9 @@ export default {
}
.refresh-btn:hover:not(:disabled), .add-btn:hover {
background: #2980b9;
background: var(--primary-dark);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
box-shadow: 0 4px 8px rgba(44, 95, 93, 0.3);
}
.refresh-btn:disabled {
@@ -1612,12 +1783,13 @@ export default {
}
.btn-primary {
background: #3498db;
background: var(--primary-color);
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #2980b9;
background: var(--primary-dark);
box-shadow: 0 4px 10px rgba(44, 95, 93, 0.3);
}
.btn-secondary {
@@ -1800,4 +1972,81 @@ export default {
gap: 0.5rem;
}
}
/* Page Content Styles */
.content-sections {
display: flex;
flex-direction: column;
gap: 2rem;
}
.content-section {
background: #f8f9fa;
padding: 1.5rem;
border-radius: 10px;
border: 1px solid #e1e8ed;
}
.content-section h3 {
color: #2c3e50;
margin-bottom: 1rem;
font-size: 1.3rem;
}
.content-section h4 {
color: #2c3e50;
margin-bottom: 0.75rem;
font-size: 1.1rem;
}
.content-form {
display: flex;
flex-direction: column;
gap: 1rem;
}
.feature-group {
background: white;
padding: 1rem;
border-radius: 8px;
border: 1px solid #e1e8ed;
}
.feature-group:not(:last-child) {
margin-bottom: 1rem;
}
.content-form .form-group {
margin-bottom: 0.5rem;
}
.content-form .form-group label {
font-weight: 600;
color: #2c3e50;
margin-bottom: 0.5rem;
display: block;
}
.content-form .form-group input,
.content-form .form-group textarea {
width: 100%;
padding: 0.75rem;
border: 2px solid #e1e8ed;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.3s ease;
}
.content-form .form-group input:focus,
.content-form .form-group textarea:focus {
outline: none;
border-color: var(--primary-color);
background: #f8f9fa;
box-shadow: 0 0 0 3px rgba(44, 95, 93, 0.1);
}
.content-form .form-group textarea {
resize: vertical;
font-family: inherit;
}
</style>
+40 -18
View File
@@ -1,8 +1,8 @@
<template>
<div class="booking">
<div class="booking-header">
<h1>🚲 Boek Je Fiets Service</h1>
<p>Vul het onderstaande formulier in om je fietsreparatie of onderhoud in te plannen</p>
<h1>{{ content.booking_header_title || '🚲 Boek Je Fiets Service' }}</h1>
<p>{{ content.booking_header_subtitle || 'Vul het onderstaande formulier in om je fietsreparatie of onderhoud in te plannen' }}</p>
</div>
<div class="booking-form-container">
@@ -181,7 +181,8 @@ export default {
const formError = ref('')
const showSuccessModal = ref(false)
const submittedRequest = ref(null)
const content = ref({})
// Date picker state
const currentDate = ref(new Date())
const blockedDates = ref([])
@@ -275,6 +276,17 @@ export default {
}
}
const fetchContent = async () => {
try {
const response = await fetch('/api/public/page-content')
if (response.ok) {
content.value = await response.json()
}
} catch (error) {
console.error('Failed to fetch page content:', error)
}
}
const previousMonth = () => {
currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() - 1, 1)
}
@@ -343,6 +355,7 @@ export default {
tomorrow.setDate(tomorrow.getDate() + 1)
form.value.requested_date = tomorrow.toISOString().split('T')[0]
fetchBlockedDates() // Fetch blocked dates on mount
fetchContent() // Fetch page content on mount
})
return {
@@ -351,6 +364,7 @@ export default {
formError,
showSuccessModal,
submittedRequest,
content,
minDate,
submitForm,
closeSuccessModal,
@@ -379,13 +393,13 @@ export default {
}
.booking-header h1 {
color: #2c3e50;
color: var(--primary-color);
font-size: 2.5rem;
margin-bottom: 1rem;
}
.booking-header p {
color: #666;
color: var(--text-gray);
font-size: 1.2rem;
}
@@ -419,7 +433,7 @@ export default {
.form-group label {
font-weight: bold;
color: #2c3e50;
color: var(--primary-color);
margin-bottom: 0.5rem;
}
@@ -435,7 +449,8 @@ export default {
.form-group input:focus,
.form-group textarea:focus {
outline: none;
border-color: #3498db;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(44, 95, 93, 0.1);
}
.form-group textarea {
@@ -476,7 +491,7 @@ export default {
.current-month {
font-weight: bold;
color: #2c3e50;
color: var(--primary-color);
text-transform: capitalize;
}
@@ -492,7 +507,7 @@ export default {
padding: 0.75rem;
text-align: center;
font-weight: bold;
color: #2c3e50;
color: var(--primary-color);
font-size: 0.875rem;
}
@@ -537,14 +552,15 @@ export default {
}
.calendar-date.selected {
background: #3498db;
background: var(--primary-color);
color: white;
font-weight: bold;
}
.calendar-date.today {
font-weight: bold;
color: #3498db;
color: var(--primary-color);
border: 2px solid var(--primary-color);
}
.calendar-date.today.selected {
@@ -566,7 +582,7 @@ export default {
}
.submit-btn {
background: #3498db;
background: var(--primary-color);
color: white;
border: none;
padding: 1rem 2rem;
@@ -574,11 +590,14 @@ export default {
font-size: 1.1rem;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s ease;
transition: all 0.3s ease;
box-shadow: 0 4px 10px rgba(44, 95, 93, 0.3);
}
.submit-btn:hover:not(:disabled) {
background: #2980b9;
background: var(--primary-dark);
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(44, 95, 93, 0.4);
}
.submit-btn:disabled {
@@ -615,7 +634,7 @@ export default {
}
.modal-header h3 {
color: #2c3e50;
color: var(--primary-color);
font-size: 1.5rem;
}
@@ -634,18 +653,21 @@ export default {
}
.btn-primary {
background: #3498db;
background: var(--primary-color);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s ease;
transition: all 0.3s ease;
box-shadow: 0 2px 6px rgba(44, 95, 93, 0.3);
}
.btn-primary:hover {
background: #2980b9;
background: var(--primary-dark);
transform: translateY(-1px);
box-shadow: 0 4px 10px rgba(44, 95, 93, 0.4);
}
@media (max-width: 768px) {
+63 -32
View File
@@ -1,46 +1,70 @@
<template>
<div class="home">
<div class="hero">
<h1>🚲 Welkom bij Fiets Service Centrum</h1>
<p>Professionele fietsreparatie en onderhoudsdiensten</p>
<h1>{{ content.home_hero_title || 'Welkom bij Fiets Service Centrum' }}</h1>
<p>{{ content.home_hero_subtitle || 'Professionele fietsreparatie en onderhoudsdiensten' }}</p>
</div>
<div class="features">
<div class="feature-card">
<h3>🔧 Deskundige Reparaties</h3>
<p>Professionele fietsmonteurs met jarenlange ervaring</p>
<h3>{{ content.home_feature_1_title || '🔧 Deskundige Reparaties' }}</h3>
<p>{{ content.home_feature_1_text || 'Professionele fietsmonteurs met jarenlange ervaring' }}</p>
</div>
<div class="feature-card">
<h3>⚡ Snelle Service</h3>
<p>Snelle doorlooptijden voor de meeste reparaties</p>
<h3>{{ content.home_feature_2_title || '⚡ Snelle Service' }}</h3>
<p>{{ content.home_feature_2_text || 'Snelle doorlooptijden voor de meeste reparaties' }}</p>
</div>
<div class="feature-card">
<h3>💰 Eerlijke Prijzen</h3>
<p>Concurrerende tarieven zonder verborgen kosten</p>
<h3>{{ content.home_feature_3_title || '💰 Eerlijke Prijzen' }}</h3>
<p>{{ content.home_feature_3_text || 'Concurrerende tarieven zonder verborgen kosten' }}</p>
</div>
<div class="feature-card">
<h3>📅 Eenvoudig Boeken</h3>
<p>Eenvoudig online boekingssysteem</p>
<h3>{{ content.home_feature_4_title || '📅 Eenvoudig Boeken' }}</h3>
<p>{{ content.home_feature_4_text || 'Eenvoudig online boekingssysteem' }}</p>
</div>
</div>
<div class="cta">
<h2>Klaar om je fiets te laten repareren?</h2>
<p>Boek je afspraak online en krijg je fiets weer in topconditie.</p>
<h2>{{ content.home_cta_title || 'Klaar om je fiets te laten repareren?' }}</h2>
<p>{{ content.home_cta_subtitle || 'Boek je afspraak online en krijg je fiets weer in topconditie.' }}</p>
<div class="cta-buttons">
<router-link to="/booking" class="cta-button">Nu Service Boeken</router-link>
<router-link to="/pricelist" class="cta-button secondary">💰 Prijslijst Bekijken</router-link>
<router-link to="/booking" class="cta-button">{{ content.home_cta_button_1_text || 'Nu Service Boeken' }}</router-link>
<router-link to="/pricelist" class="cta-button secondary">{{ content.home_cta_button_2_text || '💰 Prijslijst Bekijken' }}</router-link>
</div>
</div>
</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
name: 'Home'
name: 'Home',
setup() {
const content = ref({})
const fetchContent = async () => {
try {
const response = await fetch('/api/public/page-content')
if (response.ok) {
content.value = await response.json()
}
} catch (error) {
console.error('Failed to fetch page content:', error)
}
}
onMounted(() => {
fetchContent()
})
return {
content
}
}
}
</script>
@@ -50,11 +74,12 @@ export default {
}
.hero {
background: linear-gradient(135deg, #2c3e50 0%, #3498db 100%);
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
color: white;
padding: 4rem 2rem;
border-radius: 15px;
margin-bottom: 3rem;
box-shadow: 0 4px 20px rgba(44, 95, 93, 0.3);
}
.hero h1 {
@@ -87,30 +112,31 @@ export default {
}
.feature-card h3 {
color: #2c3e50;
color: var(--primary-color);
margin-bottom: 1rem;
font-size: 1.3rem;
}
.feature-card p {
color: #666;
color: var(--text-gray);
line-height: 1.6;
}
.cta {
background: #f8f9fa;
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
padding: 3rem 2rem;
border-radius: 15px;
border: 2px dashed #3498db;
border: 3px solid var(--primary-color);
box-shadow: 0 4px 20px rgba(44, 95, 93, 0.15);
}
.cta h2 {
color: #2c3e50;
color: var(--primary-color);
margin-bottom: 1rem;
}
.cta p {
color: #666;
color: var(--text-gray);
font-size: 1.1rem;
margin-bottom: 2rem;
}
@@ -123,25 +149,30 @@ export default {
.cta-button {
display: inline-block;
background: #3498db;
background: var(--primary-color);
color: white;
padding: 1rem 2rem;
text-decoration: none;
border-radius: 10px;
font-size: 1.1rem;
font-weight: bold;
transition: background-color 0.3s ease;
transition: all 0.3s ease;
box-shadow: 0 4px 10px rgba(44, 95, 93, 0.3);
}
.cta-button:hover {
background: #2980b9;
background: var(--primary-dark);
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(44, 95, 93, 0.4);
}
.cta-button.secondary {
background: #e74c3c;
background: var(--secondary-color);
box-shadow: 0 4px 10px rgba(139, 111, 71, 0.3);
}
.cta-button.secondary:hover {
background: #c0392b;
background: var(--secondary-dark);
box-shadow: 0 6px 15px rgba(139, 111, 71, 0.4);
}
</style>
+13 -9
View File
@@ -114,7 +114,7 @@ export default {
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #2c3e50 0%, #3498db 100%);
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
padding: 2rem;
}
@@ -133,13 +133,13 @@ export default {
}
.login-header h1 {
color: #2c3e50;
color: var(--primary-color);
font-size: 2rem;
margin-bottom: 0.5rem;
}
.login-header p {
color: #666;
color: var(--text-gray);
font-size: 1rem;
}
@@ -157,7 +157,7 @@ export default {
.form-group label {
font-weight: bold;
color: #2c3e50;
color: var(--primary-color);
font-size: 0.9rem;
}
@@ -171,7 +171,8 @@ export default {
.form-group input:focus {
outline: none;
border-color: #3498db;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(44, 95, 93, 0.1);
}
.form-group input:disabled {
@@ -190,7 +191,7 @@ export default {
}
.login-btn {
background: #3498db;
background: var(--primary-color);
color: white;
border: none;
padding: 1rem;
@@ -198,11 +199,14 @@ export default {
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s ease;
transition: all 0.3s ease;
box-shadow: 0 4px 10px rgba(44, 95, 93, 0.3);
}
.login-btn:hover:not(:disabled) {
background: #2980b9;
background: var(--primary-dark);
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(44, 95, 93, 0.4);
}
.login-btn:disabled {
@@ -225,7 +229,7 @@ export default {
}
.login-info strong {
color: #2c3e50;
color: var(--primary-color);
}
@media (max-width: 480px) {