diff --git a/app/Http/Controllers/Admin/ContentController.php b/app/Http/Controllers/Admin/ContentController.php index 9a5d4fc..3b90c41 100644 --- a/app/Http/Controllers/Admin/ContentController.php +++ b/app/Http/Controllers/Admin/ContentController.php @@ -85,6 +85,10 @@ class ContentController extends Controller 'footer_shipping_text', 'footer_copyright', 'footer_payment_text', + + // Offline / Maintenance Mode + 'shop_offline_title', + 'shop_offline_message', ]; // Handle checkbox toggles that aren't present if unchecked @@ -92,6 +96,10 @@ class ContentController extends Controller Setting::set('announcement_enabled', '0'); } + if ($request->has('shop_offline_submitted')) { + Setting::set('shop_offline', $request->boolean('shop_offline') ? '1' : '0'); + } + foreach ($fields as $field) { if ($request->has($field)) { $value = $request->input($field); @@ -168,4 +176,20 @@ class ContentController extends Controller ->with('error', "Fout bij verzenden van e-mail via {$defaultMailer}: ".$e->getMessage()); } } + + /** + * Toggle the webshop offline / online status. + */ + public function toggleOffline(Request $request): RedirectResponse + { + $current = Setting::get('shop_offline', '0') === '1'; + $new = ! $current; + Setting::set('shop_offline', $new ? '1' : '0'); + Setting::flushCache(); + + $statusText = $new ? 'OFFLINE gezet' : 'weer ONLINE gezet'; + + return redirect()->back() + ->with('success', "De webshop is direct {$statusText}!"); + } } diff --git a/app/Http/Middleware/CheckWebshopOffline.php b/app/Http/Middleware/CheckWebshopOffline.php new file mode 100644 index 0000000..4cce8e5 --- /dev/null +++ b/app/Http/Middleware/CheckWebshopOffline.php @@ -0,0 +1,75 @@ +user() && $request->user()->isAdmin()) { + return $next($request); + } + + // Keep admin, auth, health check and asset routes accessible + if ($this->isExemptRoute($request)) { + return $next($request); + } + + // Otherwise, render the large red offline maintenance screen + $title = Setting::get('shop_offline_title', 'Aan deze webshop wordt hard gewerkt! 🛠️'); + $message = Setting::get('shop_offline_message', 'Onze 3D printers worden momenteel opnieuw gekalibreerd en we bouwen aan nieuwe toffe prints en functies. We zijn heel snel weer online!'); + + return response()->view('errors.offline', [ + 'offlineTitle' => $title, + 'offlineMessage' => $message, + ], 503); + } + + /** + * Check if the current route is exempt from offline mode. + */ + protected function isExemptRoute(Request $request): bool + { + $exemptPatterns = [ + 'admin', + 'admin/*', + 'inloggen', + 'login', + 'uitloggen', + 'logout', + 'registreren', + 'register', + 'wachtwoord-*', + 'up', + 'health-debug', + 'images/*', + 'storage/*', + 'build/*', + 'favicon.ico', + ]; + + foreach ($exemptPatterns as $pattern) { + if ($request->is($pattern)) { + return true; + } + } + + return false; + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 7f30e4e..8d87bdb 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -1,5 +1,6 @@ withMiddleware(function (Middleware $middleware): void { $middleware->trustProxies(at: '*'); + $middleware->web(append: [ + CheckWebshopOffline::class, + ]); $middleware->alias([ 'admin' => EnsureUserIsAdmin::class, ]); diff --git a/resources/views/admin/content/index.blade.php b/resources/views/admin/content/index.blade.php index 6de4e84..d1d16f8 100644 --- a/resources/views/admin/content/index.blade.php +++ b/resources/views/admin/content/index.blade.php @@ -107,6 +107,16 @@ 7. E-mail & SMTP Testen + + @@ -631,6 +641,95 @@ + + + +
+
+ +
+
+
+ +

Webshop Offline Modus (Onderhoud)

+
+

+ Wanneer ingeschakeld, kunnen gewone bezoekers niets bestellen en zien zij direct de grote rode offline banner. Alleen ingelogde beheerders kunnen de website bekijken. +

+
+ + + +
+ + + + +
+
+ Status Webshop Bereikbaarheid + + {{ setting('shop_offline', '0') === '1' ? 'De webshop is momenteel OFFLINE voor gewone bezoekers.' : 'De webshop is momenteel LIVE en open voor bestellingen.' }} + +
+ + +
+ + +
+
+ + +

De grote rode hoofdtitel die bezoekers in beeld krijgen.

+
+ +
+ + +

Uitleg over waarom de webshop tijdelijk offline staat en wanneer klanten terug kunnen komen.

+
+
+ + +
+ Voorvertoning van de Offline Banner: +
+ + 🔴 Webshop Tijdelijk Offline + +

+ {{ setting('shop_offline_title', 'Aan deze webshop wordt hard gewerkt! 🛠️') }} +

+

+ {{ setting('shop_offline_message', 'Onze 3D printers worden momenteel opnieuw gekalibreerd en we bouwen aan nieuwe toffe prints en functies. We zijn heel snel weer online voor bestellingen!') }} +

+
+
+ +
+
+
diff --git a/resources/views/errors/offline.blade.php b/resources/views/errors/offline.blade.php new file mode 100644 index 0000000..7681ffb --- /dev/null +++ b/resources/views/errors/offline.blade.php @@ -0,0 +1,104 @@ + + + + + + Aan deze webshop wordt hard gewerkt! 🛠️ | Jouwdriedee.nl + + + + + + + + @vite(['resources/css/app.css', 'resources/js/app.js']) + + + + + + +
+
+ Jouwdriedee.nl +
+
+ + Onderhoudsmodus +
+
+ + +
+
+ + +
+
+ + +
+ + Webshop Tijdelijk Offline +
+ + +
+ 🖨️ +
+ + +

+ {{ $offlineTitle ?? 'Aan deze webshop wordt hard gewerkt!' }} +

+ + +

+ {{ $offlineMessage ?? 'Onze 3D printers worden momenteel opnieuw gekalibreerd en we bouwen aan nieuwe toffe prints en functies. We zijn heel snel weer online voor bestellingen!' }} +

+ + +
+
+
🛠️
+

Printstudio Update

+

Finetunen van laagdiktes en toevoegen van nieuwe multi-color designs.

+
+ +
+
📦
+

Lopende Orders

+

Reeds geplaatste bestellingen worden gewoon geprint en verzonden via PostNL.

+
+ +
+
✉️
+

Vragen of Contact?

+

+ Bereikbaar via info@jouwdriedee.nl. +

+
+
+ +
+
+ + + + + + diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php index 13b13f4..b9c32a1 100644 --- a/resources/views/layouts/admin.blade.php +++ b/resources/views/layouts/admin.blade.php @@ -76,6 +76,27 @@
+ @php + $isShopOffline = \App\Models\Setting::get('shop_offline', '0') === '1'; + @endphp + + +
+ @csrf + @if($isShopOffline) + + @else + + @endif +
+ Naar Webshop @@ -118,6 +139,24 @@
+ + @if($isShopOffline ?? (\App\Models\Setting::get('shop_offline', '0') === '1')) +
+
+ + Offline Actief + De webshop staat momenteel OFFLINE voor bezoekers. Bezoekers zien de grote rode onderhoudsbanner. Alleen jij als beheerder kunt de website bezoeken. +
+
+ @csrf + +
+
+ @endif +
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index ed04cd0..39f0dde 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -25,6 +25,27 @@ + + @if(\App\Models\Setting::get('shop_offline', '0') === '1' && auth()->check() && auth()->user()->isAdmin()) + + @endif + @if(setting('announcement_enabled', '1') == '1')
diff --git a/routes/web.php b/routes/web.php index e7ae0cd..69e34d1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -116,6 +116,7 @@ Route::prefix('admin')->name('admin.')->middleware(['auth', 'admin'])->group(fun Route::post('/content', [ContentController::class, 'update'])->name('content.update'); Route::post('/content/reset', [ContentController::class, 'reset'])->name('content.reset'); Route::post('/content/test-mail', [ContentController::class, 'sendTestMail'])->name('content.test-mail'); + Route::post('/toggle-offline', [ContentController::class, 'toggleOffline'])->name('toggle-offline'); // Producten Beheer Route::post('/products/upload-image', [AdminProductController::class, 'uploadImage'])->name('products.upload-image'); diff --git a/tests/Feature/AdminContentTest.php b/tests/Feature/AdminContentTest.php index 2d20c65..e4d4b0e 100644 --- a/tests/Feature/AdminContentTest.php +++ b/tests/Feature/AdminContentTest.php @@ -257,4 +257,50 @@ class AdminContentTest extends TestCase $storagePath = str_replace('/storage/', '', $url); Storage::disk('public')->assertExists($storagePath); } + + public function test_admin_can_toggle_webshop_offline_mode(): void + { + $this->assertEquals('0', Setting::get('shop_offline', '0')); + + // Toggle offline via admin button + $response = $this->actingAs($this->admin)->post(route('admin.toggle-offline')); + $response->assertRedirect(); + Setting::flushCache(); + $this->assertEquals('1', Setting::get('shop_offline')); + + // Logout admin so subsequent requests are unauthenticated guest requests + auth()->logout(); + $this->flushSession(); + + // Guest visitor receives 503 with big red offline banner + $guestResponse = $this->get(route('home')); + $guestResponse->assertStatus(503); + $guestResponse->assertSee('Aan deze webshop wordt hard gewerkt', false); + $guestResponse->assertSee('Webshop Tijdelijk Offline'); + + // Other customer routes are blocked + $catalogResponse = $this->get(route('catalog')); + $catalogResponse->assertStatus(503); + + // Login page remains accessible + $loginResponse = $this->get(route('login')); + $loginResponse->assertStatus(200); + + // Admin can still view storefront normally with preview banner + $adminShopResponse = $this->actingAs($this->admin)->get(route('home')); + $adminShopResponse->assertStatus(200); + $adminShopResponse->assertSee('OFFLINE BEHEERDERS-PREVIEW'); + + // Toggle back online + $this->actingAs($this->admin)->post(route('admin.toggle-offline')); + Setting::flushCache(); + $this->assertEquals('0', Setting::get('shop_offline')); + + // Logout admin and verify guest can visit normally again + auth()->logout(); + $this->flushSession(); + + $guestOnlineResponse = $this->get(route('home')); + $guestOnlineResponse->assertStatus(200); + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index fe1ffc2..6a86420 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,9 +2,14 @@ namespace Tests; +use App\Models\Setting; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase { - // + protected function setUp(): void + { + parent::setUp(); + Setting::flushCache(); + } }