Fix admin content tab retention on test mail & save, add SMTP encryption support, and provide Gmail DNS deliverability diagnostic guidance

This commit is contained in:
2026-11-02 10:02:53 +01:00
parent 9fd39bd7d6
commit 44c440222a
12 changed files with 119 additions and 28 deletions
@@ -118,8 +118,10 @@ class ContentController extends Controller
Setting::flushCache();
return redirect()->route('admin.content.index')
->with('success', 'Layout en teksten zijn direct opgeslagen en live bijgewerkt in de webshop!');
$tab = $request->input('active_tab');
$redirect = $tab ? redirect()->route('admin.content.index', ['tab' => $tab]) : redirect()->route('admin.content.index');
return $redirect->with('success', 'Layout en teksten zijn direct opgeslagen en live bijgewerkt in de webshop!');
}
/**
@@ -164,24 +166,26 @@ class ContentController extends Controller
$recipient = $validated['test_email'];
$defaultMailer = config('mail.default');
$fromAddress = config('mail.from.address');
$smtpHost = config('mail.mailers.smtp.host');
$smtpPort = config('mail.mailers.smtp.port');
try {
Mail::raw("Dit is een test e-mail vanaf Jouwdriedee.nl om de e-mail bezorging naar {$recipient} te controleren.\n\nActieve mailer: {$defaultMailer}\nAfzender: {$fromAddress}\nDatum: ".now()->format('d-m-Y H:i:s'), function ($message) use ($recipient) {
Mail::raw("Dit is een test e-mail vanaf Jouwdriedee.nl om de e-mail bezorging naar {$recipient} te controleren.\n\nActieve mailer: {$defaultMailer}\nSMTP Server: {$smtpHost}:{$smtpPort}\nAfzender: {$fromAddress}\nDatum: ".now()->format('d-m-Y H:i:s'), function ($message) use ($recipient) {
$message->to($recipient)
->subject('Test E-mail vanaf Jouwdriedee.nl');
});
if ($defaultMailer === 'log') {
return redirect()->back()
return redirect()->route('admin.content.index', ['tab' => 'email'])
->with('warning', "De testmail is weggeschreven naar de serverlogs (MAIL_MAILER staat op 'log'). Er is GEEN echte e-mail naar {$recipient} verzonden. Stel op Coolify geldige SMTP-gegevens in (bijv. Brevo, Resend of je hosting SMTP) om echte e-mails te bezorgen.");
}
return redirect()->back()
->with('success', "Test e-mail succesvol verzonden via driver '{$defaultMailer}' naar {$recipient}! Controleer je inbox en eventueel je spambox.");
return redirect()->route('admin.content.index', ['tab' => 'email'])
->with('success', "Test e-mail succesvol overhandigd aan de SMTP-server ({$smtpHost}:{$smtpPort}) voor {$recipient}! Controleer je inbox en eventueel je spamfolder.");
} catch (\Throwable $e) {
Log::error('Test mail error: '.$e->getMessage(), ['trace' => $e->getTraceAsString()]);
return redirect()->back()
return redirect()->route('admin.content.index', ['tab' => 'email'])
->with('error', "Fout bij verzenden van e-mail via {$defaultMailer}: ".$e->getMessage());
}
}
@@ -198,7 +202,7 @@ class ContentController extends Controller
$statusText = $new ? 'OFFLINE gezet' : 'weer ONLINE gezet';
return redirect()->back()
return redirect()->route('admin.content.index', ['tab' => 'offline'])
->with('success', "De webshop is direct {$statusText}!");
}
}
+1
View File
@@ -43,6 +43,7 @@ return [
'url' => env('MAIL_URL'),
'host' => env('MAIL_HOST', '127.0.0.1'),
'port' => env('MAIL_PORT', 2525),
'encryption' => env('MAIL_ENCRYPTION'),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,
@@ -220,3 +220,4 @@
</div>
@endsection
@@ -280,3 +280,4 @@
</div>
@endsection
+61 -20
View File
@@ -3,7 +3,29 @@
@section('title', 'Layout & Teksten Aanpassen | Jouwdriedee Beheer')
@section('content')
<div class="space-y-6" x-data="{ activeTab: 'hero' }">
<div class="space-y-6" x-data="{
activeTab: '{{ request('tab', session('tab', 'hero')) }}',
init() {
const urlParams = new URLSearchParams(window.location.search);
const urlTab = urlParams.get('tab');
const hash = window.location.hash ? window.location.hash.replace('#', '') : null;
const validTabs = ['hero', 'usp', 'sections', 'calculator', 'announcement', 'footer', 'brand', 'email', 'offline'];
if (urlTab && validTabs.includes(urlTab)) {
this.activeTab = urlTab;
} else if (hash && validTabs.includes(hash)) {
this.activeTab = hash;
}
},
switchTab(tab) {
this.activeTab = tab;
if (window.history.replaceState) {
const url = new URL(window.location.href);
url.searchParams.set('tab', tab);
window.history.replaceState(null, '', url.toString());
}
}
}">
<!-- Header Section -->
<div class="flex flex-col sm:flex-row sm:items-center justify-between pb-6 border-b border-slate-200/60 gap-4">
@@ -32,12 +54,13 @@
<!-- Main Form -->
<form action="{{ route('admin.content.update') }}" method="POST" class="space-y-6">
@csrf
<input type="hidden" name="active_tab" :value="activeTab">
<!-- Tabs Navigation (Sleek Segmented Strip) -->
<div class="flex items-center space-x-1 p-1 bg-slate-100/80 rounded-xl overflow-x-auto text-xs font-medium w-full max-w-full">
<button
type="button"
@click="activeTab = 'hero'"
@click="switchTab('hero')"
:class="activeTab === 'hero' ? 'bg-white text-slate-900 font-semibold shadow-2xs' : 'text-slate-600 hover:text-slate-900'"
class="px-3 py-1.5 rounded-lg transition whitespace-nowrap cursor-pointer"
>
@@ -46,7 +69,7 @@
<button
type="button"
@click="activeTab = 'usp'"
@click="switchTab('usp')"
:class="activeTab === 'usp' ? 'bg-white text-slate-900 font-semibold shadow-2xs' : 'text-slate-600 hover:text-slate-900'"
class="px-3 py-1.5 rounded-lg transition whitespace-nowrap cursor-pointer"
>
@@ -55,7 +78,7 @@
<button
type="button"
@click="activeTab = 'sections'"
@click="switchTab('sections')"
:class="activeTab === 'sections' ? 'bg-white text-slate-900 font-semibold shadow-2xs' : 'text-slate-600 hover:text-slate-900'"
class="px-3 py-1.5 rounded-lg transition whitespace-nowrap cursor-pointer"
>
@@ -64,7 +87,7 @@
<button
type="button"
@click="activeTab = 'calculator'"
@click="switchTab('calculator')"
:class="activeTab === 'calculator' ? 'bg-white text-slate-900 font-semibold shadow-2xs' : 'text-slate-600 hover:text-slate-900'"
class="px-3 py-1.5 rounded-lg transition whitespace-nowrap cursor-pointer"
>
@@ -73,7 +96,7 @@
<button
type="button"
@click="activeTab = 'announcement'"
@click="switchTab('announcement')"
:class="activeTab === 'announcement' ? 'bg-white text-slate-900 font-semibold shadow-2xs' : 'text-slate-600 hover:text-slate-900'"
class="px-3 py-1.5 rounded-lg transition whitespace-nowrap cursor-pointer"
>
@@ -82,7 +105,7 @@
<button
type="button"
@click="activeTab = 'footer'"
@click="switchTab('footer')"
:class="activeTab === 'footer' ? 'bg-white text-slate-900 font-semibold shadow-2xs' : 'text-slate-600 hover:text-slate-900'"
class="px-3 py-1.5 rounded-lg transition whitespace-nowrap cursor-pointer"
>
@@ -91,7 +114,7 @@
<button
type="button"
@click="activeTab = 'brand'"
@click="switchTab('brand')"
:class="activeTab === 'brand' ? 'bg-white text-slate-900 font-semibold shadow-2xs' : 'text-slate-600 hover:text-slate-900'"
class="px-3 py-1.5 rounded-lg transition whitespace-nowrap cursor-pointer"
>
@@ -100,7 +123,7 @@
<button
type="button"
@click="activeTab = 'email'"
@click="switchTab('email')"
:class="activeTab === 'email' ? 'bg-white text-slate-900 font-semibold shadow-2xs' : 'text-slate-600 hover:text-slate-900'"
class="px-3 py-1.5 rounded-lg transition whitespace-nowrap cursor-pointer"
>
@@ -109,7 +132,7 @@
<button
type="button"
@click="activeTab = 'offline'"
@click="switchTab('offline')"
:class="activeTab === 'offline' ? 'bg-rose-500 text-white font-semibold shadow-2xs' : 'text-rose-600 hover:bg-rose-50'"
class="px-3 py-1.5 rounded-lg transition whitespace-nowrap cursor-pointer flex items-center space-x-1.5"
>
@@ -657,10 +680,10 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-xs">
<div class="p-4 rounded-xl bg-slate-50/70 border border-slate-200/60 space-y-2">
<div class="font-semibold text-slate-500 text-[11px] uppercase tracking-wider">Huidige Mail Instellingen</div>
<div class="font-semibold text-slate-500 text-[11px] uppercase tracking-wider">Huidige Mail & SMTP Instellingen</div>
<div class="flex justify-between py-1 border-b border-slate-200/50">
<span class="text-slate-500">MAIL_MAILER:</span>
<span class="font-mono text-slate-900">{{ config('mail.default') }}</span>
<span class="font-mono font-semibold {{ config('mail.default') === 'log' ? 'text-amber-600' : 'text-emerald-700' }}">{{ config('mail.default') }}</span>
</div>
<div class="flex justify-between py-1 border-b border-slate-200/50">
<span class="text-slate-500">MAIL_HOST:</span>
@@ -670,6 +693,14 @@
<span class="text-slate-500">MAIL_PORT:</span>
<span class="font-mono text-slate-900">{{ config('mail.mailers.smtp.port') ?: '(geen)' }}</span>
</div>
<div class="flex justify-between py-1 border-b border-slate-200/50">
<span class="text-slate-500">MAIL_ENCRYPTION:</span>
<span class="font-mono text-slate-900">{{ config('mail.mailers.smtp.encryption') ?: (config('mail.mailers.smtp.scheme') ?: '(automatisch)') }}</span>
</div>
<div class="flex justify-between py-1 border-b border-slate-200/50">
<span class="text-slate-500">MAIL_USERNAME:</span>
<span class="font-mono text-slate-900">{{ config('mail.mailers.smtp.username') ?: '(geen)' }}</span>
</div>
<div class="flex justify-between py-1 border-b border-slate-200/50">
<span class="text-slate-500">MAIL_FROM:</span>
<span class="font-mono text-slate-900">{{ config('mail.from.address') }}</span>
@@ -677,19 +708,28 @@
</div>
<div class="p-4 rounded-xl bg-slate-50/70 border border-slate-200/60 space-y-2">
<div class="font-semibold text-slate-500 text-[11px] uppercase tracking-wider">Gmail Afleverchecklist</div>
<ul class="space-y-1.5 text-slate-600 text-[11px]">
<div class="font-semibold text-slate-500 text-[11px] uppercase tracking-wider">Waarom komt mail niet aan op Gmail?</div>
<p class="text-[11px] text-slate-600 leading-relaxed">
Als je wel een groene bevestiging krijgt maar niks ontvangt in Gmail, dan accepteert de SMTP-server de mail, maar weigert of filtert Google de bezorging door <strong>ontbrekende of foutieve DNS-records</strong>:
</p>
<ul class="space-y-1.5 text-slate-600 text-[11px] pt-1">
<li class="flex items-start space-x-2">
<span class="text-slate-900 font-semibold">1.</span>
<span><strong>SMTP Provider:</strong> Gebruik een geverifieerde mailserver.</span>
<span class="text-rose-600 font-semibold shrink-0">⚠️ SPF Error:</span>
<span>In je DNS staat <code class="bg-white border px-1 rounded font-mono text-[10px]">include:_spf.jouwdriedee.nl</code>, maar dit subdomein bestaat niet! Dit veroorzaakt een fatale <em>SPF PermError</em> bij Google.</span>
</li>
<li class="flex items-start space-x-2">
<span class="text-slate-900 font-semibold">2.</span>
<span><strong>SPF Record:</strong> Google vereist een SPF TXT-record op het domein.</span>
<span class="text-slate-900 font-semibold shrink-0">1. Oplossing SPF:</span>
<span>Zet in je Versio DNS het TXT record voor <code class="bg-white border px-1 rounded font-mono text-[10px]">@</code> op:<br>
<code class="bg-white border px-1 py-0.5 rounded font-mono text-[10px] text-slate-900 block mt-1 select-all">v=spf1 a mx ip4:185.182.56.214 include:spf.versio.nl ~all</code></span>
</li>
<li class="flex items-start space-x-2">
<span class="text-slate-900 font-semibold">3.</span>
<span><strong>DKIM & DMARC:</strong> Noodzakelijk om spamfilters van Gmail te passeren.</span>
<span class="text-slate-900 font-semibold shrink-0">2. DMARC toevoegen:</span>
<span>Maak een TXT record met naam <code class="bg-white border px-1 rounded font-mono text-[10px]">_dmarc</code> en waarde:<br>
<code class="bg-white border px-1 py-0.5 rounded font-mono text-[10px] text-slate-900 block mt-1 select-all">v=DMARC1; p=none; rua=mailto:info@jouwdriedee.nl</code></span>
</li>
<li class="flex items-start space-x-2">
<span class="text-slate-900 font-semibold shrink-0">3. Spamfolder:</span>
<span>Controleer altijd ook je Gmail Spam/Ongewenst map.</span>
</li>
</ul>
</div>
@@ -698,6 +738,7 @@
<!-- Test Email Form -->
<div class="p-4 rounded-xl bg-slate-50/70 border border-slate-200/60 space-y-2">
<span class="font-semibold text-slate-900 text-xs block">Test E-mail Verzenden</span>
<p class="text-[11px] text-slate-500">Verzend een directe testmail om de verbinding en aflevering te verifiëren. Na verzending blijft deze pagina netjes op tabblad 8 geopend.</p>
<div class="flex flex-col sm:flex-row gap-2 pt-1">
<input
type="email"
@@ -148,3 +148,4 @@
</div>
@endsection
+1
View File
@@ -250,3 +250,4 @@
</div>
@endsection
@@ -65,3 +65,4 @@
</tr>
</table>
@endsection
@@ -79,3 +79,4 @@
</tr>
</table>
@endsection
@@ -47,3 +47,4 @@
</tr>
</table>
@endsection
+14
View File
@@ -206,6 +206,20 @@
</div>
@endif
@if(session('warning'))
<div class="mb-6 p-4 rounded-xl bg-amber-50/80 border border-amber-200/60 text-amber-800 text-xs font-medium flex items-center space-x-2.5 shadow-2xs">
<i data-lucide="alert-triangle" class="w-4 h-4 text-amber-600 shrink-0"></i>
<span>{{ session('warning') }}</span>
</div>
@endif
@if(session('info'))
<div class="mb-6 p-4 rounded-xl bg-blue-50/80 border border-blue-200/60 text-blue-800 text-xs font-medium flex items-center space-x-2.5 shadow-2xs">
<i data-lucide="info" class="w-4 h-4 text-blue-600 shrink-0"></i>
<span>{{ session('info') }}</span>
</div>
@endif
@if(session('error'))
<div class="mb-6 p-4 rounded-xl bg-rose-50/80 border border-rose-200/60 text-rose-800 text-xs font-medium flex items-center space-x-2.5 shadow-2xs">
<i data-lucide="alert-circle" class="w-4 h-4 text-rose-600 shrink-0"></i>
+24
View File
@@ -10,6 +10,7 @@ use App\Models\User;
use App\Services\PriceCalculatorService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
@@ -407,4 +408,27 @@ class AdminContentTest extends TestCase
$this->assertEquals(45, (int) Setting::get('calc_default_markup'));
$this->assertEquals(8, (int) Setting::get('calc_failure_rate'));
}
public function test_sending_test_mail_redirects_and_stays_on_email_tab(): void
{
Mail::fake();
$response = $this->actingAs($this->admin)->post(route('admin.content.test-mail'), [
'test_email' => 'niek.rabelink@gmail.com',
]);
$response->assertRedirect(route('admin.content.index', ['tab' => 'email']));
$response->assertSessionHas('success');
}
public function test_updating_content_with_active_tab_preserves_tab(): void
{
$response = $this->actingAs($this->admin)->post(route('admin.content.update'), [
'active_tab' => 'calculator',
'site_name' => 'Jouwdriedee 3D Print Studio',
]);
$response->assertRedirect(route('admin.content.index', ['tab' => 'calculator']));
$response->assertSessionHas('success');
}
}