diff --git a/app/Http/Controllers/Admin/CategoryController.php b/app/Http/Controllers/Admin/CategoryController.php new file mode 100644 index 0000000..595a418 --- /dev/null +++ b/app/Http/Controllers/Admin/CategoryController.php @@ -0,0 +1,48 @@ +get(); + + return view('admin.categories.index', compact('categories')); + } + + /** + * Show the form for editing the category. + */ + public function edit(Category $category): View + { + return view('admin.categories.edit', compact('category')); + } + + /** + * Update the category in storage. + */ + public function update(Request $request, Category $category): RedirectResponse + { + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'description' => 'nullable|string', + 'icon' => 'nullable|string|max:50', + 'sort_order' => 'nullable|integer', + ]); + + $category->update($validated); + + return redirect()->route('admin.categories.index') + ->with('success', "Categorie '{$category->name}' is succesvol bijgewerkt!"); + } +} diff --git a/app/Http/Controllers/Admin/ContentController.php b/app/Http/Controllers/Admin/ContentController.php new file mode 100644 index 0000000..cd2101c --- /dev/null +++ b/app/Http/Controllers/Admin/ContentController.php @@ -0,0 +1,135 @@ +has('announcement_enabled')) { + Setting::set('announcement_enabled', '0'); + } + + foreach ($fields as $field) { + if ($request->has($field)) { + $value = $request->input($field); + Setting::set($field, is_string($value) ? trim($value) : $value); + } + } + + Setting::flushCache(); + + return redirect()->route('admin.content.index') + ->with('success', 'Layout en teksten zijn direct opgeslagen en live bijgewerkt in de webshop!'); + } + + /** + * Reset specific settings or all back to default. + */ + public function reset(): RedirectResponse + { + $keys = [ + 'site_name', 'site_tagline', 'meta_description', 'theme_palette', + 'announcement_enabled', 'announcement_text', 'announcement_badge_1', 'announcement_badge_2', + 'hero_badge', 'hero_title', 'hero_subtitle', 'hero_primary_btn_text', 'hero_primary_btn_url', + 'hero_secondary_btn_text', 'hero_secondary_btn_url', + 'hero_stat_1_val', 'hero_stat_1_lbl', 'hero_stat_2_val', 'hero_stat_2_lbl', 'hero_stat_3_val', 'hero_stat_3_lbl', + 'hero_card_badge', + 'categories_tag', 'categories_title', 'categories_subtitle', + 'featured_tag', 'featured_title', 'featured_subtitle', + 'usp_tag', 'usp_title', 'usp_subtitle', + 'usp_1_title', 'usp_1_desc', 'usp_2_title', 'usp_2_desc', 'usp_3_title', 'usp_3_desc', 'usp_4_title', 'usp_4_desc', + 'usp_btn_text', + 'footer_description', 'footer_status_text', 'footer_email', 'footer_shipping_text', 'footer_copyright', 'footer_payment_text', + ]; + + foreach ($keys as $key) { + Setting::forget($key); + } + + Setting::flushCache(); + + return redirect()->route('admin.content.index') + ->with('success', 'Alle teksten en lay-out instellingen zijn hersteld naar de standaarden.'); + } +} diff --git a/app/Http/Controllers/Admin/OrderController.php b/app/Http/Controllers/Admin/OrderController.php new file mode 100644 index 0000000..07a8fb3 --- /dev/null +++ b/app/Http/Controllers/Admin/OrderController.php @@ -0,0 +1,53 @@ +latest(); + + if ($request->filled('status')) { + $query->where('status', $request->input('status')); + } + + $orders = $query->paginate(20)->withQueryString(); + + return view('admin.orders.index', compact('orders')); + } + + /** + * Display order details. + */ + public function show(Order $order): View + { + $order->load('items.product'); + + return view('admin.orders.show', compact('order')); + } + + /** + * Update order status. + */ + public function updateStatus(Request $request, Order $order): RedirectResponse + { + $validated = $request->validate([ + 'status' => 'required|string|in:pending,processing,printed,shipped,completed,cancelled', + ]); + + $order->update(['status' => $validated['status']]); + + return redirect()->route('admin.orders.show', $order) + ->with('success', "Bestelling #{$order->order_number} status is gewijzigd naar '{$validated['status']}'!"); + } +} diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php new file mode 100644 index 0000000..ad6cfab --- /dev/null +++ b/app/Http/Controllers/Admin/ProductController.php @@ -0,0 +1,134 @@ +latest(); + + if ($request->filled('q')) { + $q = $request->input('q'); + $query->where(function ($w) use ($q) { + $w->where('name', 'like', "%{$q}%") + ->orWhere('description', 'like', "%{$q}%"); + }); + } + + if ($request->filled('category_id')) { + $query->where('category_id', $request->input('category_id')); + } + + $products = $query->paginate(20)->withQueryString(); + $categories = Category::all(); + + return view('admin.products.index', compact('products', 'categories')); + } + + /** + * Show the form for creating a new product. + */ + public function create(): View + { + $categories = Category::all(); + + return view('admin.products.create', compact('categories')); + } + + /** + * Store a newly created product in storage. + */ + public function store(Request $request): RedirectResponse + { + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'category_id' => 'required|exists:categories,id', + 'base_price' => 'required|numeric|min:0.01', + 'short_description' => 'nullable|string|max:500', + 'description' => 'required|string', + 'image_url' => 'nullable|string|max:500', + 'dimensions' => 'nullable|string|max:100', + 'weight_grams' => 'nullable|integer|min:0', + 'print_time_hours' => 'nullable|numeric|min:0', + 'is_featured' => 'nullable|boolean', + 'is_active' => 'nullable|boolean', + ]); + + $validated['slug'] = Str::slug($validated['name']); + $validated['is_featured'] = $request->boolean('is_featured'); + $validated['is_active'] = $request->boolean('is_active'); + $validated['available_materials'] = ['pla', 'petg', 'silk', 'tpu']; + $validated['available_colors'] = [ + 'Zon Geel (Pikachu)', 'Vurig Rood (Charizard)', 'Hemelsblauw (Squirtle)', + 'Muntgroen (Bulbasaur)', 'Spooky Paars (Gengar)', 'Koper Zijdeglans', + 'Regenboog Multi-Color', 'Mat Zwart', 'Sneeuwwit', + ]; + + Product::create($validated); + + return redirect()->route('admin.products.index') + ->with('success', "Product '{$validated['name']}' is succesvol aangemaakt!"); + } + + /** + * Show the form for editing the specified product. + */ + public function edit(Product $product): View + { + $categories = Category::all(); + + return view('admin.products.edit', compact('product', 'categories')); + } + + /** + * Update the specified product in storage. + */ + public function update(Request $request, Product $product): RedirectResponse + { + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'category_id' => 'required|exists:categories,id', + 'base_price' => 'required|numeric|min:0.01', + 'short_description' => 'nullable|string|max:500', + 'description' => 'required|string', + 'image_url' => 'nullable|string|max:500', + 'dimensions' => 'nullable|string|max:100', + 'weight_grams' => 'nullable|integer|min:0', + 'print_time_hours' => 'nullable|numeric|min:0', + 'is_featured' => 'nullable|boolean', + 'is_active' => 'nullable|boolean', + ]); + + $validated['is_featured'] = $request->boolean('is_featured'); + $validated['is_active'] = $request->boolean('is_active'); + + $product->update($validated); + + return redirect()->route('admin.products.index') + ->with('success', "Product '{$product->name}' is succesvol bijgewerkt!"); + } + + /** + * Remove the specified product from storage. + */ + public function destroy(Product $product): RedirectResponse + { + $name = $product->name; + $product->delete(); + + return redirect()->route('admin.products.index') + ->with('success', "Product '{$name}' is verwijderd."); + } +} diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 71356ec..1478014 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -3,6 +3,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +use Throwable; class Setting extends Model { @@ -17,23 +18,50 @@ class Setting extends Model 'value', ]; + /** + * @var array|null + */ + protected static ?array $runtimeCache = null; + + public static function flushCache(): void + { + static::$runtimeCache = null; + } + public static function get(string $key, mixed $default = null): mixed { - $setting = static::query()->find($key); + if (static::$runtimeCache === null) { + try { + static::$runtimeCache = static::query()->pluck('value', 'key')->all(); + } catch (Throwable) { + return $default; + } + } - if (! $setting || $setting->value === null) { + if (! array_key_exists($key, static::$runtimeCache) || static::$runtimeCache[$key] === null) { return $default; } - $decoded = json_decode($setting->value, true); + $val = static::$runtimeCache[$key]; - return (json_last_error() === JSON_ERROR_NONE) ? $decoded : $setting->value; + if (is_string($val) && (str_starts_with($val, '{') || str_starts_with($val, '['))) { + $decoded = json_decode($val, true); + if (json_last_error() === JSON_ERROR_NONE) { + return $decoded; + } + } + + return $val; } public static function set(string $key, mixed $value): static { $encoded = is_array($value) || is_object($value) ? json_encode($value) : (string) $value; + if (static::$runtimeCache !== null) { + static::$runtimeCache[$key] = $encoded; + } + return static::query()->updateOrCreate( ['key' => $key], ['value' => $encoded] @@ -42,6 +70,10 @@ class Setting extends Model public static function forget(string $key): bool { + if (static::$runtimeCache !== null) { + unset(static::$runtimeCache[$key]); + } + return (bool) static::query()->where('key', $key)->delete(); } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 1ea0978..70dc822 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -20,6 +20,8 @@ class AppServiceProvider extends ServiceProvider */ public function boot(): void { + require_once app_path('helpers.php'); + if (app()->environment('production') || str_starts_with(config('app.url', ''), 'https://')) { URL::forceScheme('https'); } diff --git a/app/helpers.php b/app/helpers.php new file mode 100644 index 0000000..f9ae67c --- /dev/null +++ b/app/helpers.php @@ -0,0 +1,10 @@ +name . ' | Jouwdriedee Beheer') + +@section('content') +
+ + +
+
+ + + Terug naar Categorieën + +

Categorie Bewerken: {{ $category->name }}

+
+
+ + +
+ @csrf + @method('PUT') + +
+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+ +
+ + +
+
+ +
+ +
+ + Annuleren + + +
+ +
+
+@endsection diff --git a/resources/views/admin/categories/index.blade.php b/resources/views/admin/categories/index.blade.php new file mode 100644 index 0000000..2e1a8ab --- /dev/null +++ b/resources/views/admin/categories/index.blade.php @@ -0,0 +1,53 @@ +@extends('layouts.admin') + +@section('title', 'Categorieën Beheren | Jouwdriedee Beheer') + +@section('content') +
+ + +
+
+
+ + Winkelstructuur +
+

Categorieën Beheer

+

Beheer categorie titels, omschrijvingen en sortering.

+
+
+ + +
+ @foreach($categories as $category) +
+
+
+ + /catalogus?category={{ $category->slug }} + + + {{ $category->products_count }} producten + +
+ +

{{ $category->name }}

+

{{ $category->description }}

+
+ + +
+ @endforeach +
+ +
+@endsection diff --git a/resources/views/admin/content/index.blade.php b/resources/views/admin/content/index.blade.php new file mode 100644 index 0000000..38a371e --- /dev/null +++ b/resources/views/admin/content/index.blade.php @@ -0,0 +1,528 @@ +@extends('layouts.admin') + +@section('title', 'Layout & Teksten Aanpassen | Jouwdriedee Beheer') + +@section('content') +
+ + +
+
+
+ + Shop Content & Stijl CMS +
+

Layout & Teksten Beheer

+

+ Pas alle teksten, titels, USP's, call-to-actions en de lay-out van jouw webshop direct aan. Wijzigingen zijn onmiddellijk live zichtbaar voor bezoekers! +

+
+ +
+ + + Bekijk Live Shop + +
+ @csrf + +
+
+
+ + +
+ @csrf + + +
+ + + + + + + + + + + +
+ + + + +
+
+
+

Homepage Hero Bovenkant

+

De eerste sectie die bezoekers zien bij het openen van de winkel.

+
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + +

Kies een pakkende, speelse titel die meteen duidelijk maakt wat er te koop is.

+
+ +
+ + +
+ + +
+
+ Primaire Actieknop (Gele knop) +
+ + +
+
+ + +
+
+ +
+ Secundaire Actieknop (Lila knop) +
+ + +
+
+ + +
+
+
+ + +
+ 3 Kwaliteitskenmerken onder de knoppen +
+
+ + + +
+ +
+ + + +
+ +
+ + + +
+
+
+ +
+
+ + + + +
+
+
+

Waarom Jouwdriedee? (Voordelen & USP's)

+

De 4 kleurrijke informatieblokken onderaan de homepage.

+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ + +
+ +
+ Voordeel 1 (Paars blok) + + +
+ + +
+ Voordeel 2 (Geel blok) + + +
+ + +
+ Voordeel 3 (Groen blok) + + +
+ + +
+ Voordeel 4 (Blauw blok) + + +
+
+ +
+ + +
+ +
+
+ + + + +
+
+
+

Categorieën & Collecties Sectie

+

Titels van het blok met Fidget Toys, Pokémon en Film & Gaming.

+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ +
+
+

Populaire Modellen (Uitgelichte Prints)

+

Titels van de product-showcase op de homepage.

+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ + + + +
+
+
+

Bovenbalk (Top Announcement Bar)

+

De dunne balk helemaal bovenaan de website voor mededelingen en levertijd.

+
+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+

Voettekst (Footer Informatie & Contact)

+

Teksten in de donkere voettekst onderaan alle pagina's.

+
+ +
+ + +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+
+ + + + +
+
+
+

Algemeen & Branding

+

Shopnaam, zoekmachine omschrijving en algemene instellingen.

+
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ + @php $currentTheme = setting('theme_palette', 'default'); @endphp + +
+ +
+
+ + +
+
+ + Wijzigingen worden direct live opgeslagen in de database. +
+ +
+ +
+
+@endsection diff --git a/resources/views/admin/makerworld/index.blade.php b/resources/views/admin/makerworld/index.blade.php index 6e85117..a3d4fbf 100644 --- a/resources/views/admin/makerworld/index.blade.php +++ b/resources/views/admin/makerworld/index.blade.php @@ -1,4 +1,4 @@ -@extends('layouts.app') +@extends('layouts.admin') @section('title', 'MakerWorld Modellen Importeren | Jouwdriedee.nl Beheer') diff --git a/resources/views/admin/orders/index.blade.php b/resources/views/admin/orders/index.blade.php new file mode 100644 index 0000000..8be9068 --- /dev/null +++ b/resources/views/admin/orders/index.blade.php @@ -0,0 +1,92 @@ +@extends('layouts.admin') + +@section('title', 'Bestellingen | Jouwdriedee Beheer') + +@section('content') +
+ + +
+
+
+ + Verkopen & Facturatie +
+

Bestellingen ({{ $orders->total() }})

+

Inzicht in binnengekomen bestellingen, status en klantgegevens.

+
+
+ + +
+
+ + + + + + + + + + + + + + @forelse($orders as $order) + + + + + + + + + + @empty + + + + @endforelse + +
Order #DatumKlantItemsTotaalStatusDetails
+ {{ $order->order_number }} + + {{ $order->created_at->format('d-m-Y H:i') }} + +
{{ $order->customer_name }}
+
{{ $order->customer_email }}
+
+ + {{ $order->items->count() }} items + + + {{ $order->formatted_total }} + + + {{ $order->status }} + + + + Bekijken + + +
+ Nog geen bestellingen binnengekomen. +
+
+ + @if($orders->hasPages()) +
+ {{ $orders->links() }} +
+ @endif +
+ +
+@endsection diff --git a/resources/views/admin/orders/show.blade.php b/resources/views/admin/orders/show.blade.php new file mode 100644 index 0000000..039ca22 --- /dev/null +++ b/resources/views/admin/orders/show.blade.php @@ -0,0 +1,116 @@ +@extends('layouts.admin') + +@section('title', 'Bestelling #' . $order->order_number . ' | Jouwdriedee Beheer') + +@section('content') +
+ + +
+
+ + + Terug naar Bestellingen + +

Bestelling #{{ $order->order_number }}

+

Geplaatst op {{ $order->created_at->format('d-m-Y om H:i') }}

+
+ + +
+ @csrf + + +
+
+ +
+ + +
+
+

Bestelde 3D Prints

+ +
+ @foreach($order->items as $item) +
+
+

{{ $item->product_name }}

+
+
Materiaal: {{ strtoupper($item->material) }}
+
Kleur: {{ $item->color }}
+
Schaal: {{ $item->scale ?? '100%' }}
+
+
+
+ + {{ $item->quantity }}x € {{ number_format($item->unit_price, 2, ',', '.') }} + +
+ € {{ number_format($item->total_price, 2, ',', '.') }} +
+
+
+ @endforeach +
+ +
+ Totaalbedrag + {{ $order->formatted_total }} +
+
+
+ + +
+
+

Klant & Verzendadres

+ +
+
+ Naam + {{ $order->customer_name }} +
+ +
+ E-mail + {{ $order->customer_email }} +
+ + @if($order->customer_phone) +
+ Telefoon + {{ $order->customer_phone }} +
+ @endif + +
+ Adres +
{{ $order->shipping_street }} {{ $order->shipping_number }}
+
{{ $order->shipping_postal_code }} {{ $order->shipping_city }}
+
{{ $order->shipping_country ?? 'Nederland' }}
+
+
+ + @if($order->notes) +
+ Klant Opmerking +

{{ $order->notes }}

+
+ @endif +
+
+ +
+ +
+@endsection diff --git a/resources/views/admin/products/create.blade.php b/resources/views/admin/products/create.blade.php new file mode 100644 index 0000000..30de8b3 --- /dev/null +++ b/resources/views/admin/products/create.blade.php @@ -0,0 +1,110 @@ +@extends('layouts.admin') + +@section('title', 'Nieuw Product Toevoegen | Jouwdriedee Beheer') + +@section('content') +
+ + +
+
+ + + Terug naar Producten + +

Nieuw Product Toevoegen

+
+
+ + +
+ @csrf + +
+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ + + +
+ +
+ +
+ + Annuleren + + +
+ +
+
+@endsection diff --git a/resources/views/admin/products/edit.blade.php b/resources/views/admin/products/edit.blade.php new file mode 100644 index 0000000..1e1358e --- /dev/null +++ b/resources/views/admin/products/edit.blade.php @@ -0,0 +1,116 @@ +@extends('layouts.admin') + +@section('title', 'Product Bewerken: ' . $product->name . ' | Jouwdriedee Beheer') + +@section('content') +
+ + +
+
+ + + Terug naar Producten + +

Product Bewerken: {{ $product->name }}

+
+ + + Bekijk Live + + +
+ + +
+ @csrf + @method('PUT') + +
+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ + + +
+ +
+ +
+ + Annuleren + + +
+ +
+
+@endsection diff --git a/resources/views/admin/products/index.blade.php b/resources/views/admin/products/index.blade.php new file mode 100644 index 0000000..0b87a13 --- /dev/null +++ b/resources/views/admin/products/index.blade.php @@ -0,0 +1,170 @@ +@extends('layouts.admin') + +@section('title', 'Producten Beheren | Jouwdriedee Beheer') + +@section('content') +
+ + +
+
+
+ + Assortiment & Voorraad +
+

Producten Overzicht ({{ $products->total() }})

+

Beheer prijzen, teksten, specificaties en afbeeldingen van alle 3D prints.

+
+ + +
+ + +
+
+
+ + +
+ + + + + @if(request()->hasAny(['q', 'category_id'])) + Wissen + @endif +
+
+ + +
+
+ + + + + + + + + + + + + @forelse($products as $product) + + + + + + + + + + + + + + @empty + + + + @endforelse + +
ProductCategorieBasisprijsSpecificatiesStatusActies
+
+
+ {{ $product->name }} +
+
+ + {{ $product->name }} + + /product/{{ $product->slug }} +
+
+
+ @if($product->category) + + {{ $product->category->name }} + + @else + - + @endif + + {{ $product->formatted_price }} + +
{{ $product->dimensions ?? 'Standaard' }}
+
~{{ $product->weight_grams ?? 0 }}g • {{ $product->print_time_hours ?? 0 }}u
+
+
+ @if($product->is_active) + + + Actief + + @else + + + Verborgen + + @endif + + @if($product->is_featured) + + ★ Uitgelicht + + @endif +
+
+
+ + + + + + +
+ @csrf + @method('DELETE') + +
+
+
+ Geen producten gevonden. +
+
+ + @if($products->hasPages()) +
+ {{ $products->links() }} +
+ @endif +
+ +
+@endsection diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php new file mode 100644 index 0000000..62f78f8 --- /dev/null +++ b/resources/views/layouts/admin.blade.php @@ -0,0 +1,132 @@ + + + + + + @yield('title', 'Admin Dashboard | Jouwdriedee.nl') + + + + + + + + @vite(['resources/css/app.css', 'resources/js/app.js']) + + + + + + +
+ + + + +
+ + +
+ + + @if(session('success')) +
+
+ + {{ session('success') }} +
+ + Bekijk Live + + +
+ @endif + + @if(session('error')) +
+ + {{ session('error') }} +
+ @endif + + @yield('content') +
+ + + + + diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 8563167..bfba5d9 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -3,8 +3,8 @@ - @yield('title', 'Jouwdriedee.nl | Kleurrijke 3D Prints, Fidget Toys & Collectibles') - + @yield('title', setting('site_name', 'Jouwdriedee.nl') . ' | ' . setting('site_tagline', 'Kleurrijke 3D Prints, Fidget Toys & Collectibles')) + @@ -25,25 +25,31 @@ - -
-
-
- - Nederlandse 3D Printstudio • Haarscherpe afwerking op Bambu Lab printers -
- + @endif
@@ -53,7 +59,7 @@
- Jouwdriedee.nl + {{ setting('site_name', 'Jouwdriedee.nl') }} @@ -84,7 +90,7 @@ @@ -120,7 +126,7 @@ @@ -175,7 +181,7 @@ @yield('content') - +
@@ -183,14 +189,14 @@
- Jouwdriedee.nl + {{ setting('site_name', 'Jouwdriedee.nl') }}

- Gespecialiseerd in het 3D printen van gedetailleerde modellen, soepel bewegende fidget toys en iconische popculture figuren. Gemaakt met hoogwaardige filamenten op hypermoderne FDM-printers. + {{ setting('footer_description', 'Gespecialiseerd in het 3D printen van gedetailleerde modellen, soepel bewegende fidget toys en iconische popculture figuren. Gemaakt met hoogwaardige filamenten op hypermoderne FDM-printers.') }}

- Printers gekalibreerd & actief + {{ setting('footer_status_text', 'Printers gekalibreerd & actief') }}
@@ -235,11 +241,11 @@
- info@jouwdriedee.nl + {{ setting('footer_email', 'info@jouwdriedee.nl') }}
- Snelle verzending via PostNL + {{ setting('footer_shipping_text', 'Snelle verzending via PostNL') }}
@@ -247,11 +253,9 @@
-

© {{ date('Y') }} Jouwdriedee.nl. Alle 3D prints worden op bestelling vervaardigd.

+

© {{ date('Y') }} {{ setting('site_name', 'Jouwdriedee.nl') }}. {{ setting('footer_copyright', 'Alle 3D prints worden op bestelling vervaardigd.') }}

- iDEAL & Bancontact - • - PostNL Track & Trace + {{ setting('footer_payment_text', 'iDEAL & Bancontact • PostNL Track & Trace') }}
diff --git a/resources/views/shop/index.blade.php b/resources/views/shop/index.blade.php index 431cf6a..07e3820 100644 --- a/resources/views/shop/index.blade.php +++ b/resources/views/shop/index.blade.php @@ -1,9 +1,9 @@ @extends('layouts.app') -@section('title', 'Jouwdriedee.nl | Kleurrijke 3D Prints, Fidget Toys & Collectibles') +@section('title', setting('site_name', 'Jouwdriedee.nl') . ' | ' . setting('site_tagline', 'Kleurrijke 3D Prints, Fidget Toys & Collectibles')) @section('content') - +
@@ -13,26 +13,25 @@
- Nederlandse 3D Printstudio • Bambu Lab Precisie + {{ setting('hero_badge', 'Nederlandse 3D Printstudio • Bambu Lab Precisie') }}

- Kleurrijke 3D prints, - fidget toys & collectibles. + {!! nl2br(e(setting('hero_title', 'Kleurrijke 3D prints, fidget toys & collectibles.'))) !!}

- Van soepel bewegende kristaldraken en geometrische Pokémon planters tot filmprops op schaal. Haarscherp geprint in hoogwaardig Bio-PLA+, stevig PETG of flexibel TPU in jouw favoriete kleur! + {{ setting('hero_subtitle', 'Van soepel bewegende kristaldraken en geometrische Pokémon planters tot filmprops op schaal. Haarscherp geprint in hoogwaardig Bio-PLA+, stevig PETG of flexibel TPU in jouw favoriete kleur!') }}

@@ -40,16 +39,28 @@
-
0.12 mm
-
Minimale laaghoogte
+
+ {{ setting('hero_stat_1_val', '0.12 mm') }} +
+
+ {{ setting('hero_stat_1_lbl', 'Minimale laaghoogte') }} +
-
Bambu Lab
-
X1-Carbon Precisie
+
+ {{ setting('hero_stat_2_val', 'Bambu Lab') }} +
+
+ {{ setting('hero_stat_2_lbl', 'X1-Carbon Precisie') }} +
-
2-3 Dagen
-
Verzending via PostNL
+
+ {{ setting('hero_stat_3_val', '2-3 Dagen') }} +
+
+ {{ setting('hero_stat_3_lbl', 'Verzending via PostNL') }} +
@@ -60,7 +71,7 @@
- Meest gekozen print + {{ setting('hero_card_badge', 'Meest gekozen print') }}
@@ -98,14 +109,20 @@
- +
- Collecties -

Ontdek per categorie

-

Selecteer een categorie voor alle beschikbare modellen, formaten en kleurstalen.

+ + {{ setting('categories_tag', 'Collecties') }} + +

+ {{ setting('categories_title', 'Ontdek per categorie') }} +

+

+ {{ setting('categories_subtitle', 'Selecteer een categorie voor alle beschikbare modellen, formaten en kleurstalen.') }} +

Alle prints bekijken @@ -170,9 +187,15 @@
- +
- Waarom Jouwdriedee.nl? + + {{ setting('usp_tag', 'Waarom Jouwdriedee.nl?') }} +

- Kwaliteit en passie in elke printlaag + {{ setting('usp_title', 'Kwaliteit en passie in elke printlaag') }}

- Geen vage levertijden of ruwe prints. Elk model wordt in eigen werkplaats gekalibreerd, geprint en gecontroleerd voordat het veilig verpakt op de post gaat. + {{ setting('usp_subtitle', 'Geen vage levertijden of ruwe prints. Elk model wordt in eigen werkplaats gekalibreerd, geprint en gecontroleerd voordat het veilig verpakt op de post gaat.') }}

@@ -264,8 +289,12 @@
-

Bambu Lab Precisie

-

Haarscherpe lagen vanaf 0.12mm. Vaste toleranties waardoor scharnieren moeiteloos bewegen.

+

+ {{ setting('usp_1_title', 'Bambu Lab Precisie') }} +

+

+ {{ setting('usp_1_desc', 'Haarscherpe lagen vanaf 0.12mm. Vaste toleranties waardoor scharnieren moeiteloos bewegen.') }} +

@@ -273,8 +302,12 @@
-

16+ Kleurvariaties

-

Van glanzend Silk Goud en Galaxy Zwart tot mat pastel en glow-in-the-dark filament.

+

+ {{ setting('usp_2_title', '16+ Kleurvariaties') }} +

+

+ {{ setting('usp_2_desc', 'Van glanzend Silk Goud en Galaxy Zwart tot mat pastel en glow-in-the-dark filament.') }} +

@@ -282,8 +315,12 @@
-

Veilig & Duurzaam PLA+

-

Gemaakt van hernieuwbare plantaardige zetmelen. Geurloos, gifvrij en stevig in gebruik.

+

+ {{ setting('usp_3_title', 'Veilig & Duurzaam PLA+') }} +

+

+ {{ setting('usp_3_desc', 'Gemaakt van hernieuwbare plantaardige zetmelen. Geurloos, gifvrij en stevig in gebruik.') }} +

@@ -291,14 +328,18 @@
-

Snelle Verzending

-

Met zorg verpakt in beschermend materiaal. Binnen 2-3 werkdagen verzonden via PostNL.

+

+ {{ setting('usp_4_title', 'Snelle Verzending') }} +

+

+ {{ setting('usp_4_desc', 'Met zorg verpakt in beschermend materiaal. Binnen 2-3 werkdagen verzonden via PostNL.') }} +

- Bekijk alle 3D prints in de winkel + {{ setting('usp_btn_text', 'Bekijk alle 3D prints in de winkel') }}
diff --git a/routes/web.php b/routes/web.php index c8e01be..41bb181 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,6 +1,10 @@ name('checkout.i Route::post('/afrekenen', [CheckoutController::class, 'store'])->name('checkout.store'); Route::get('/bestelling-ontvangen/{orderNumber}', [CheckoutController::class, 'success'])->name('checkout.success'); -// Admin & MakerWorld Beheer +// Admin & CMS Beheer Route::prefix('admin')->name('admin.')->group(function () { - Route::redirect('/', '/admin/makerworld'); + Route::redirect('/', '/admin/content'); + + // Layout, Stijl & Teksten CMS + Route::get('/content', [ContentController::class, 'index'])->name('content.index'); + Route::post('/content', [ContentController::class, 'update'])->name('content.update'); + Route::post('/content/reset', [ContentController::class, 'reset'])->name('content.reset'); + + // Producten Beheer + Route::resource('products', AdminProductController::class)->except(['show']); + + // Categorieën Beheer + Route::resource('categories', AdminCategoryController::class)->only(['index', 'edit', 'update']); + + // Bestellingen Beheer + Route::get('/orders', [AdminOrderController::class, 'index'])->name('orders.index'); + Route::get('/orders/{order}', [AdminOrderController::class, 'show'])->name('orders.show'); + Route::post('/orders/{order}/status', [AdminOrderController::class, 'updateStatus'])->name('orders.status'); + + // MakerWorld Importer Route::get('/makerworld', [MakerWorldController::class, 'index'])->name('makerworld.index'); Route::post('/makerworld/login', [MakerWorldController::class, 'login'])->name('makerworld.login'); Route::post('/makerworld/verify-code', [MakerWorldController::class, 'verifyCode'])->name('makerworld.verify-code'); diff --git a/tests/Feature/AdminContentTest.php b/tests/Feature/AdminContentTest.php new file mode 100644 index 0000000..258856e --- /dev/null +++ b/tests/Feature/AdminContentTest.php @@ -0,0 +1,140 @@ +get(route('admin.content.index')); + + $response->assertStatus(200); + $response->assertSee('Homepage Hero'); + $response->assertSee('Shop Content & Stijl CMS', false); + } + + public function test_admin_can_update_layout_and_texts(): void + { + $payload = [ + 'site_name' => 'Mega 3D Shop', + 'hero_title' => 'Custom 3D Speelgoed & Figuren', + 'hero_subtitle' => 'Nieuwe custom introductietekst voor de winkel.', + 'hero_primary_btn_text' => 'Direct Winkelen', + 'usp_title' => 'Waarom kiezen voor onze prints?', + 'footer_email' => 'hallo@mega3d.nl', + 'announcement_enabled' => '1', + 'announcement_text' => 'Tijdelijke actie: gratis verzending!', + ]; + + $response = $this->post(route('admin.content.update'), $payload); + + $response->assertRedirect(route('admin.content.index')); + $response->assertSessionHas('success'); + + $this->assertSame('Mega 3D Shop', Setting::get('site_name')); + $this->assertSame('Custom 3D Speelgoed & Figuren', Setting::get('hero_title')); + $this->assertSame('hallo@mega3d.nl', Setting::get('footer_email')); + + // Homepage should now display the updated content! + $homeResponse = $this->get(route('home')); + $homeResponse->assertStatus(200); + $homeResponse->assertSee('Custom 3D Speelgoed & Figuren'); + $homeResponse->assertSee('Nieuwe custom introductietekst voor de winkel.'); + $homeResponse->assertSee('Direct Winkelen'); + $homeResponse->assertSee('Waarom kiezen voor onze prints?'); + $homeResponse->assertSee('hallo@mega3d.nl'); + $homeResponse->assertSee('Tijdelijke actie: gratis verzending!'); + } + + public function test_admin_can_reset_content_to_defaults(): void + { + Setting::set('hero_title', 'Tijdelijke Titel'); + $this->assertSame('Tijdelijke Titel', Setting::get('hero_title')); + + $response = $this->post(route('admin.content.reset')); + $response->assertRedirect(route('admin.content.index')); + + $this->assertNull(Setting::get('hero_title')); + } + + public function test_admin_can_view_and_edit_products(): void + { + $category = Category::create([ + 'name' => 'Fidgets', + 'slug' => 'fidgets', + ]); + + $product = Product::create([ + 'name' => 'Test Fidget Draak', + 'slug' => 'test-fidget-draak', + 'category_id' => $category->id, + 'base_price' => 12.50, + 'short_description' => 'Korte test', + 'description' => 'Uitgebreide test beschrijving', + 'is_active' => true, + ]); + + // Products index + $indexResponse = $this->get(route('admin.products.index')); + $indexResponse->assertStatus(200); + $indexResponse->assertSee('Test Fidget Draak'); + + // Edit page + $editResponse = $this->get(route('admin.products.edit', $product)); + $editResponse->assertStatus(200); + $editResponse->assertSee('Test Fidget Draak'); + + // Update product + $updateResponse = $this->put(route('admin.products.update', $product), [ + 'name' => 'Aangepaste Fidget Draak', + 'category_id' => $category->id, + 'base_price' => 18.00, + 'short_description' => 'Geüpdatete korte test', + 'description' => 'Nieuwe lange beschrijving', + 'is_active' => 1, + 'is_featured' => 1, + ]); + + $updateResponse->assertRedirect(route('admin.products.index')); + $this->assertDatabaseHas('products', [ + 'id' => $product->id, + 'name' => 'Aangepaste Fidget Draak', + 'base_price' => 18.00, + ]); + } + + public function test_admin_can_view_orders(): void + { + $order = Order::create([ + 'order_number' => 'ORD-TEST-999', + 'customer_name' => 'Jan Jansen', + 'customer_email' => 'jan@example.com', + 'shipping_street' => 'Dorpstraat', + 'shipping_number' => '1', + 'shipping_postal_code' => '1234AB', + 'shipping_city' => 'Amsterdam', + 'subtotal' => 25.00, + 'shipping_cost' => 0.00, + 'total' => 25.00, + 'status' => 'pending', + ]); + + $response = $this->get(route('admin.orders.index')); + $response->assertStatus(200); + $response->assertSee('ORD-TEST-999'); + $response->assertSee('Jan Jansen'); + + $showResponse = $this->get(route('admin.orders.show', $order)); + $showResponse->assertStatus(200); + $showResponse->assertSee('ORD-TEST-999'); + } +}