feat(api): organisation email branding and shared mail layout
- Add email branding columns to organisations table (logo, color, reply-to, sender name, footer)
- Create MailBrandingService for resolving per-org branding with defaults
- Create CrewliMailable abstract base class with branded from/reply-to
- Create shared Blade layout (mail.layouts.crewli) with inline CSS
- Refactor Registration*Mail and InvitationMail to extend CrewliMailable
- Add config/crewli.php for platform-wide defaults (portal_url, app_url, logo)
- Add dev-only /mail-preview/{type} route for browser email previewing
- Update Organisation model, resource, and form requests with branding fields
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
15
api/resources/views/mail/invitation.blade.php
Normal file
15
api/resources/views/mail/invitation.blade.php
Normal file
@@ -0,0 +1,15 @@
|
||||
@extends('mail.layouts.crewli')
|
||||
|
||||
@section('title')
|
||||
Je bent uitgenodigd!
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<p style="margin: 0 0 16px;">{{ $inviterName }} heeft je uitgenodigd om deel te nemen als <strong>{{ $role }}</strong>.</p>
|
||||
|
||||
<p style="margin: 0;">Deze uitnodiging verloopt op <strong>{{ $expiresAt->format('d-m-Y H:i') }}</strong> (over 7 dagen).</p>
|
||||
@endsection
|
||||
|
||||
@section('action')
|
||||
<a href="{{ $acceptUrl }}" style="display: inline-block; padding: 12px 24px; background-color: {{ $branding['primary_color'] }}; color: #ffffff; text-decoration: none; border-radius: 6px; font-size: 15px; font-weight: 600;">Uitnodiging accepteren</a>
|
||||
@endsection
|
||||
70
api/resources/views/mail/layouts/crewli.blade.php
Normal file
70
api/resources/views/mail/layouts/crewli.blade.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>@yield('title')</title>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; background-color: #f4f4f5; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color: #f4f4f5;">
|
||||
<tr>
|
||||
<td align="center" style="padding: 32px 16px;">
|
||||
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="max-width: 600px; width: 100%; background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.08);">
|
||||
{{-- Accent line --}}
|
||||
<tr>
|
||||
<td style="height: 4px; background-color: {{ $branding['primary_color'] }};"></td>
|
||||
</tr>
|
||||
|
||||
{{-- Header: logo + org name --}}
|
||||
<tr>
|
||||
<td align="center" style="padding: 32px 40px 24px;">
|
||||
@if($branding['logo_url'])
|
||||
<img src="{{ $branding['logo_url'] }}" alt="{{ $branding['organisation_name'] }}" style="max-height: 60px; max-width: 200px; display: block; margin-bottom: 12px;">
|
||||
@endif
|
||||
<p style="margin: 0; font-size: 15px; color: #6b7280; font-weight: 500;">{{ $branding['organisation_name'] }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{{-- Body --}}
|
||||
<tr>
|
||||
<td style="padding: 0 40px 32px;">
|
||||
{{-- Title --}}
|
||||
<h1 style="margin: 0 0 20px; font-size: 22px; font-weight: 700; color: #1f2937; line-height: 1.3;">
|
||||
@yield('title')
|
||||
</h1>
|
||||
|
||||
{{-- Content --}}
|
||||
<div style="font-size: 16px; color: #1f2937; line-height: 1.6;">
|
||||
@yield('content')
|
||||
</div>
|
||||
|
||||
{{-- Action button --}}
|
||||
@hasSection('action')
|
||||
<div style="margin-top: 28px;">
|
||||
@yield('action')
|
||||
</div>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{{-- Footer --}}
|
||||
<tr>
|
||||
<td style="padding: 24px 40px; border-top: 1px solid #e5e7eb;">
|
||||
@if($branding['footer_text'])
|
||||
<p style="margin: 0 0 16px; font-size: 13px; color: #6b7280; text-align: center; line-height: 1.5;">
|
||||
{{ $branding['footer_text'] }}
|
||||
</p>
|
||||
@endif
|
||||
|
||||
<p style="margin: 0; font-size: 11px; color: #9ca3af; text-align: center; line-height: 1.5;">
|
||||
Powered by Crewli<br>
|
||||
Je ontvangt deze email omdat je bent aangemeld bij {{ $branding['organisation_name'] }}.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
17
api/resources/views/mail/registration-approved.blade.php
Normal file
17
api/resources/views/mail/registration-approved.blade.php
Normal file
@@ -0,0 +1,17 @@
|
||||
@extends('mail.layouts.crewli')
|
||||
|
||||
@section('title')
|
||||
Goed nieuws!
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<p style="margin: 0 0 16px;">Beste {{ $person->first_name }},</p>
|
||||
|
||||
<p style="margin: 0 0 16px;">Goed nieuws! Je bent goedgekeurd als vrijwilliger voor <strong>{{ $event->name }}</strong>.</p>
|
||||
|
||||
<p style="margin: 0;">Log in op het portaal om je shifts te bekijken en te claimen.</p>
|
||||
@endsection
|
||||
|
||||
@section('action')
|
||||
<a href="{{ $portalUrl }}" style="display: inline-block; padding: 12px 24px; background-color: {{ $branding['primary_color'] }}; color: #ffffff; text-decoration: none; border-radius: 6px; font-size: 15px; font-weight: 600;">Naar het portaal</a>
|
||||
@endsection
|
||||
24
api/resources/views/mail/registration-confirmation.blade.php
Normal file
24
api/resources/views/mail/registration-confirmation.blade.php
Normal file
@@ -0,0 +1,24 @@
|
||||
@extends('mail.layouts.crewli')
|
||||
|
||||
@section('title')
|
||||
Bedankt voor je aanmelding!
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<p style="margin: 0 0 16px;">Beste {{ $person->first_name }},</p>
|
||||
|
||||
<p style="margin: 0 0 16px;">Bedankt voor je aanmelding als vrijwilliger voor <strong>{{ $event->name }}</strong>!</p>
|
||||
|
||||
<p style="margin: 0 0 16px;">Je registratie wordt beoordeeld door de organisatie. Je ontvangt een e-mail zodra je aanmelding is verwerkt.</p>
|
||||
|
||||
<p style="margin: 0 0 16px;">Je kunt inloggen op het vrijwilligersportaal om je status te volgen.</p>
|
||||
|
||||
<p style="margin: 0;">
|
||||
<strong>Evenement:</strong> {{ $event->name }}<br>
|
||||
<strong>Datum:</strong> {{ $event->start_date->format('d-m-Y') }} t/m {{ $event->end_date->format('d-m-Y') }}
|
||||
</p>
|
||||
@endsection
|
||||
|
||||
@section('action')
|
||||
<a href="{{ $portalUrl }}" style="display: inline-block; padding: 12px 24px; background-color: {{ $branding['primary_color'] }}; color: #ffffff; text-decoration: none; border-radius: 6px; font-size: 15px; font-weight: 600;">Naar het portaal</a>
|
||||
@endsection
|
||||
17
api/resources/views/mail/registration-rejected.blade.php
Normal file
17
api/resources/views/mail/registration-rejected.blade.php
Normal file
@@ -0,0 +1,17 @@
|
||||
@extends('mail.layouts.crewli')
|
||||
|
||||
@section('title')
|
||||
Update over je aanmelding
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<p style="margin: 0 0 16px;">Beste {{ $person->first_name }},</p>
|
||||
|
||||
<p style="margin: 0 0 16px;">Helaas hebben we je aanmelding voor <strong>{{ $event->name }}</strong> niet kunnen goedkeuren.</p>
|
||||
|
||||
@if($reason)
|
||||
<p style="margin: 0 0 16px;"><strong>Reden:</strong> {{ $reason }}</p>
|
||||
@endif
|
||||
|
||||
<p style="margin: 0;">Neem contact op met de organisatie als je vragen hebt.</p>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user