Converted all notification emails to the branded layout by routing them through a shared Blade template and swapping
the MailMessage builders to use view(). This keeps the existing copy/labels but aligns the look with resources/views/ emails/partials/layout.blade.php. I also switched the customer add‑on receipt notification to reuse the existing branded view and added missing translations for the upload pipeline alert.
This commit is contained in:
@@ -82,6 +82,10 @@ class EventMemberController extends Controller
|
||||
|
||||
$member->refresh();
|
||||
|
||||
if (! $user->hasVerifiedEmail()) {
|
||||
$user->sendEmailVerificationNotification();
|
||||
}
|
||||
|
||||
return (new EventMemberResource($member))->response()->setStatusCode(201);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use App\Notifications\VerifyEmailNotification;
|
||||
use Filament\Models\Contracts\FilamentUser;
|
||||
use Filament\Models\Contracts\HasName;
|
||||
use Filament\Models\Contracts\HasTenants as FilamentHasTenants;
|
||||
@@ -92,6 +93,11 @@ class User extends Authenticatable implements FilamentHasTenants, FilamentUser,
|
||||
return null;
|
||||
}
|
||||
|
||||
public function sendEmailVerificationNotification(): void
|
||||
{
|
||||
$this->notify(new VerifyEmailNotification);
|
||||
}
|
||||
|
||||
protected function fullName(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
|
||||
@@ -30,18 +30,8 @@ class AddonPurchaseReceipt extends Notification implements ShouldQueue
|
||||
|
||||
return (new MailMessage)
|
||||
->subject(__('emails.addons.receipt.subject', ['addon' => $label]))
|
||||
->greeting(__('emails.addons.receipt.greeting', ['name' => $tenant?->name ?? __('emails.package_limits.team_fallback')]))
|
||||
->line(__('emails.addons.receipt.body', [
|
||||
'addon' => $label,
|
||||
'event' => $event?->name['de'] ?? $event?->name['en'] ?? $event?->name ?? __('emails.package_limits.event_fallback'),
|
||||
'amount' => $amount ? $amount.' '.$currency : __('emails.addons.receipt.unknown_amount'),
|
||||
]))
|
||||
->line(__('emails.addons.receipt.summary', [
|
||||
'photos' => $this->addon->extra_photos,
|
||||
'guests' => $this->addon->extra_guests,
|
||||
'days' => $this->addon->extra_gallery_days,
|
||||
]))
|
||||
->action(__('emails.addons.receipt.action'), $url)
|
||||
->line(__('emails.package_limits.footer'));
|
||||
->view('emails.addons.receipt', [
|
||||
'addon' => $this->addon,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,20 +27,29 @@ class RefundReceipt extends Notification implements ShouldQueue
|
||||
$tenant = $this->purchase->tenant;
|
||||
$package = $this->purchase->package;
|
||||
$amount = number_format((float) $this->purchase->price, 2);
|
||||
|
||||
$mail = (new MailMessage)
|
||||
->subject(__('emails.refund.subject', ['package' => $package?->name ?? __('emails.package_limits.package_fallback')]))
|
||||
->greeting(__('emails.refund.greeting', ['name' => $tenant?->name ?? __('emails.package_limits.team_fallback')]))
|
||||
->line(__('emails.refund.body', [
|
||||
$subject = __('emails.refund.subject', ['package' => $package?->name ?? __('emails.package_limits.package_fallback')]);
|
||||
$greeting = __('emails.refund.greeting', ['name' => $tenant?->name ?? __('emails.package_limits.team_fallback')]);
|
||||
$lines = [
|
||||
__('emails.refund.body', [
|
||||
'amount' => $amount,
|
||||
'currency' => '€',
|
||||
'provider_id' => $this->purchase->provider_id ?? '—',
|
||||
]));
|
||||
]),
|
||||
];
|
||||
|
||||
if ($this->reason) {
|
||||
$mail->line(__('emails.refund.reason', ['reason' => $this->reason]));
|
||||
$lines[] = __('emails.refund.reason', ['reason' => $this->reason]);
|
||||
}
|
||||
|
||||
return $mail->line(__('emails.refund.footer'));
|
||||
return (new MailMessage)
|
||||
->subject($subject)
|
||||
->view('emails.notifications.basic', [
|
||||
'title' => $subject,
|
||||
'preheader' => $greeting,
|
||||
'heroTitle' => $greeting,
|
||||
'heroSubtitle' => $subject,
|
||||
'lines' => $lines,
|
||||
'footer' => __('emails.refund.footer'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,13 +27,25 @@ class InactiveTenantDeletionWarning extends Notification implements ShouldQueue
|
||||
{
|
||||
$locale = $this->tenant->user?->preferred_locale ?? app()->getLocale();
|
||||
$formattedDate = $this->plannedDeletion->copy()->locale($locale)->translatedFormat('d. F Y');
|
||||
$subject = __('profile.retention.warning_subject', [], $locale);
|
||||
|
||||
return (new MailMessage)
|
||||
->locale($locale)
|
||||
->subject(__('profile.retention.warning_subject', [], $locale))
|
||||
->line(__('profile.retention.line1', ['name' => $this->tenant->name], $locale))
|
||||
->line(__('profile.retention.line2', ['date' => $formattedDate], $locale))
|
||||
->line(__('profile.retention.line3', [], $locale))
|
||||
->action(__('profile.retention.action', [], $locale), url('/login'));
|
||||
->subject($subject)
|
||||
->view('emails.notifications.basic', [
|
||||
'title' => $subject,
|
||||
'preheader' => __('profile.retention.line1', ['name' => $this->tenant->name], $locale),
|
||||
'heroTitle' => $subject,
|
||||
'lines' => [
|
||||
__('profile.retention.line1', ['name' => $this->tenant->name], $locale),
|
||||
__('profile.retention.line2', ['date' => $formattedDate], $locale),
|
||||
__('profile.retention.line3', [], $locale),
|
||||
],
|
||||
'cta' => [
|
||||
[
|
||||
'label' => __('profile.retention.action', [], $locale),
|
||||
'url' => url('/login'),
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,18 +26,32 @@ class AddonPurchased extends Notification implements ShouldQueue
|
||||
$label = $this->addon->metadata['label'] ?? $this->addon->addon_key;
|
||||
$amount = $this->addon->amount ? number_format((float) $this->addon->amount, 2) : null;
|
||||
$currency = $this->addon->currency ?? 'EUR';
|
||||
$subject = __('emails.ops.addon.subject', ['addon' => $label]);
|
||||
$greeting = __('emails.ops.addon.greeting');
|
||||
$lines = [
|
||||
__('emails.ops.addon.tenant', ['tenant' => $tenant?->name ?? __('emails.tenant_feedback.unknown_tenant')]),
|
||||
__('emails.ops.addon.event', ['event' => $event?->name['de'] ?? $event?->name['en'] ?? $event?->name ?? __('emails.package_limits.event_fallback')]),
|
||||
__('emails.ops.addon.addon', ['addon' => $label, 'quantity' => $this->addon->quantity]),
|
||||
];
|
||||
|
||||
if ($amount) {
|
||||
$lines[] = __('emails.ops.addon.amount', ['amount' => $amount, 'currency' => $currency]);
|
||||
}
|
||||
|
||||
$lines[] = __('emails.ops.addon.provider', [
|
||||
'checkout' => $this->addon->checkout_id ?? '—',
|
||||
'transaction' => $this->addon->transaction_id ?? '—',
|
||||
]);
|
||||
|
||||
return (new MailMessage)
|
||||
->subject(__('emails.ops.addon.subject', ['addon' => $label]))
|
||||
->greeting(__('emails.ops.addon.greeting'))
|
||||
->line(__('emails.ops.addon.tenant', ['tenant' => $tenant?->name ?? __('emails.tenant_feedback.unknown_tenant')]))
|
||||
->line(__('emails.ops.addon.event', ['event' => $event?->name['de'] ?? $event?->name['en'] ?? $event?->name ?? __('emails.package_limits.event_fallback')]))
|
||||
->line(__('emails.ops.addon.addon', ['addon' => $label, 'quantity' => $this->addon->quantity]))
|
||||
->when($amount, fn ($mail) => $mail->line(__('emails.ops.addon.amount', ['amount' => $amount, 'currency' => $currency])))
|
||||
->line(__('emails.ops.addon.provider', [
|
||||
'checkout' => $this->addon->checkout_id ?? '—',
|
||||
'transaction' => $this->addon->transaction_id ?? '—',
|
||||
]))
|
||||
->line(__('emails.ops.addon.footer'));
|
||||
->subject($subject)
|
||||
->view('emails.notifications.basic', [
|
||||
'title' => $subject,
|
||||
'preheader' => $greeting,
|
||||
'heroTitle' => $greeting,
|
||||
'heroSubtitle' => $subject,
|
||||
'lines' => $lines,
|
||||
'footer' => __('emails.ops.addon.footer'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,19 +25,28 @@ class PurchaseCreated extends Notification implements ShouldQueue
|
||||
$package = $this->purchase->package;
|
||||
$amount = number_format((float) $this->purchase->price, 2);
|
||||
$consents = $this->purchase->metadata['consents'] ?? [];
|
||||
$subject = __('emails.ops.purchase.subject', ['package' => $package?->name ?? __('emails.package_limits.package_fallback')]);
|
||||
$greeting = __('emails.ops.purchase.greeting');
|
||||
|
||||
return (new MailMessage)
|
||||
->subject(__('emails.ops.purchase.subject', ['package' => $package?->name ?? __('emails.package_limits.package_fallback')]))
|
||||
->greeting(__('emails.ops.purchase.greeting'))
|
||||
->line(__('emails.ops.purchase.tenant', ['tenant' => $tenant?->name ?? __('emails.tenant_feedback.unknown_tenant')]))
|
||||
->line(__('emails.ops.purchase.package', ['package' => $package?->name ?? __('emails.package_limits.package_fallback')]))
|
||||
->line(__('emails.ops.purchase.amount', ['amount' => $amount, 'currency' => '€']))
|
||||
->line(__('emails.ops.purchase.provider', ['provider' => $this->purchase->provider, 'id' => $this->purchase->provider_id ?? '—']))
|
||||
->line(__('emails.ops.purchase.consents', [
|
||||
'legal' => $consents['legal_version'] ?? 'n/a',
|
||||
'terms' => $consents['accepted_terms_at'] ?? 'n/a',
|
||||
'waiver' => $consents['digital_content_waiver_at'] ?? 'n/a',
|
||||
]))
|
||||
->line(__('emails.ops.purchase.footer'));
|
||||
->subject($subject)
|
||||
->view('emails.notifications.basic', [
|
||||
'title' => $subject,
|
||||
'preheader' => $greeting,
|
||||
'heroTitle' => $greeting,
|
||||
'heroSubtitle' => $subject,
|
||||
'lines' => [
|
||||
__('emails.ops.purchase.tenant', ['tenant' => $tenant?->name ?? __('emails.tenant_feedback.unknown_tenant')]),
|
||||
__('emails.ops.purchase.package', ['package' => $package?->name ?? __('emails.package_limits.package_fallback')]),
|
||||
__('emails.ops.purchase.amount', ['amount' => $amount, 'currency' => '€']),
|
||||
__('emails.ops.purchase.provider', ['provider' => $this->purchase->provider, 'id' => $this->purchase->provider_id ?? '—']),
|
||||
__('emails.ops.purchase.consents', [
|
||||
'legal' => $consents['legal_version'] ?? 'n/a',
|
||||
'terms' => $consents['accepted_terms_at'] ?? 'n/a',
|
||||
'waiver' => $consents['digital_content_waiver_at'] ?? 'n/a',
|
||||
]),
|
||||
],
|
||||
'footer' => __('emails.ops.purchase.footer'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,24 +29,33 @@ class RefundProcessed extends Notification implements ShouldQueue
|
||||
$tenant = $this->purchase->tenant;
|
||||
$package = $this->purchase->package;
|
||||
$amount = number_format((float) $this->purchase->price, 2);
|
||||
|
||||
$mail = (new MailMessage)
|
||||
->subject(__('emails.ops.refund.subject', ['package' => $package?->name ?? __('emails.package_limits.package_fallback')]))
|
||||
->greeting(__('emails.ops.refund.greeting'))
|
||||
->line(__('emails.ops.refund.tenant', ['tenant' => $tenant?->name ?? __('emails.tenant_feedback.unknown_tenant')]))
|
||||
->line(__('emails.ops.refund.package', ['package' => $package?->name ?? __('emails.package_limits.package_fallback')]))
|
||||
->line(__('emails.ops.refund.amount', ['amount' => $amount, 'currency' => '€']))
|
||||
->line(__('emails.ops.refund.provider', ['provider' => $this->purchase->provider, 'id' => $this->purchase->provider_id ?? '—']))
|
||||
->line($this->success ? __('emails.ops.refund.status_success') : __('emails.ops.refund.status_failed'));
|
||||
$subject = __('emails.ops.refund.subject', ['package' => $package?->name ?? __('emails.package_limits.package_fallback')]);
|
||||
$greeting = __('emails.ops.refund.greeting');
|
||||
$lines = [
|
||||
__('emails.ops.refund.tenant', ['tenant' => $tenant?->name ?? __('emails.tenant_feedback.unknown_tenant')]),
|
||||
__('emails.ops.refund.package', ['package' => $package?->name ?? __('emails.package_limits.package_fallback')]),
|
||||
__('emails.ops.refund.amount', ['amount' => $amount, 'currency' => '€']),
|
||||
__('emails.ops.refund.provider', ['provider' => $this->purchase->provider, 'id' => $this->purchase->provider_id ?? '—']),
|
||||
$this->success ? __('emails.ops.refund.status_success') : __('emails.ops.refund.status_failed'),
|
||||
];
|
||||
|
||||
if ($this->reason) {
|
||||
$mail->line(__('emails.ops.refund.reason', ['reason' => $this->reason]));
|
||||
$lines[] = __('emails.ops.refund.reason', ['reason' => $this->reason]);
|
||||
}
|
||||
|
||||
if ($this->error) {
|
||||
$mail->line(__('emails.ops.refund.error', ['error' => $this->error]));
|
||||
$lines[] = __('emails.ops.refund.error', ['error' => $this->error]);
|
||||
}
|
||||
|
||||
return $mail->line(__('emails.ops.refund.footer'));
|
||||
return (new MailMessage)
|
||||
->subject($subject)
|
||||
->view('emails.notifications.basic', [
|
||||
'title' => $subject,
|
||||
'preheader' => $greeting,
|
||||
'heroTitle' => $greeting,
|
||||
'heroSubtitle' => $subject,
|
||||
'lines' => $lines,
|
||||
'footer' => __('emails.ops.refund.footer'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,20 +27,34 @@ class EventPackageGalleryExpiredNotification extends Notification implements Sho
|
||||
|
||||
$eventName = $event?->name['de'] ?? $event?->name['en'] ?? $event?->name ?? __('emails.package_limits.event_fallback');
|
||||
$url = url('/tenant/events/'.($event?->slug ?? ''));
|
||||
$subject = __('emails.package_limits.gallery_expired.subject', [
|
||||
'event' => $eventName,
|
||||
]);
|
||||
$greeting = __('emails.package_limits.gallery_expired.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]);
|
||||
|
||||
return (new MailMessage)
|
||||
->subject(__('emails.package_limits.gallery_expired.subject', [
|
||||
'event' => $eventName,
|
||||
]))
|
||||
->greeting(__('emails.package_limits.gallery_expired.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]))
|
||||
->line(__('emails.package_limits.gallery_expired.body', [
|
||||
'event' => $eventName,
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'date' => optional($this->eventPackage->gallery_expires_at)->toFormattedDateString(),
|
||||
]))
|
||||
->action(__('emails.package_limits.gallery_expired.action'), $url)
|
||||
->line(__('emails.package_limits.footer'));
|
||||
->subject($subject)
|
||||
->view('emails.notifications.basic', [
|
||||
'title' => $subject,
|
||||
'preheader' => $greeting,
|
||||
'heroTitle' => $greeting,
|
||||
'heroSubtitle' => $subject,
|
||||
'lines' => [
|
||||
__('emails.package_limits.gallery_expired.body', [
|
||||
'event' => $eventName,
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'date' => optional($this->eventPackage->gallery_expires_at)->toFormattedDateString(),
|
||||
]),
|
||||
],
|
||||
'cta' => [
|
||||
[
|
||||
'label' => __('emails.package_limits.gallery_expired.action'),
|
||||
'url' => $url,
|
||||
],
|
||||
],
|
||||
'footer' => __('emails.package_limits.footer'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,22 +30,36 @@ class EventPackageGalleryExpiringNotification extends Notification implements Sh
|
||||
|
||||
$eventName = $event?->name['de'] ?? $event?->name['en'] ?? $event?->name ?? __('emails.package_limits.event_fallback');
|
||||
$url = url('/tenant/events/'.($event?->slug ?? ''));
|
||||
$subject = trans_choice('emails.package_limits.gallery_warning.subject', $this->daysRemaining, [
|
||||
'event' => $eventName,
|
||||
'days' => $this->daysRemaining,
|
||||
]);
|
||||
$greeting = __('emails.package_limits.gallery_warning.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]);
|
||||
|
||||
return (new MailMessage)
|
||||
->subject(trans_choice('emails.package_limits.gallery_warning.subject', $this->daysRemaining, [
|
||||
'event' => $eventName,
|
||||
'days' => $this->daysRemaining,
|
||||
]))
|
||||
->greeting(__('emails.package_limits.gallery_warning.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]))
|
||||
->line(trans_choice('emails.package_limits.gallery_warning.body', $this->daysRemaining, [
|
||||
'event' => $eventName,
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'days' => $this->daysRemaining,
|
||||
'date' => optional($this->eventPackage->gallery_expires_at)->toFormattedDateString(),
|
||||
]))
|
||||
->action(__('emails.package_limits.gallery_warning.action'), $url)
|
||||
->line(__('emails.package_limits.footer'));
|
||||
->subject($subject)
|
||||
->view('emails.notifications.basic', [
|
||||
'title' => $subject,
|
||||
'preheader' => $greeting,
|
||||
'heroTitle' => $greeting,
|
||||
'heroSubtitle' => $subject,
|
||||
'lines' => [
|
||||
trans_choice('emails.package_limits.gallery_warning.body', $this->daysRemaining, [
|
||||
'event' => $eventName,
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'days' => $this->daysRemaining,
|
||||
'date' => optional($this->eventPackage->gallery_expires_at)->toFormattedDateString(),
|
||||
]),
|
||||
],
|
||||
'cta' => [
|
||||
[
|
||||
'label' => __('emails.package_limits.gallery_warning.action'),
|
||||
'url' => $url,
|
||||
],
|
||||
],
|
||||
'footer' => __('emails.package_limits.footer'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,22 +30,39 @@ class EventPackageGuestLimitNotification extends Notification implements ShouldQ
|
||||
|
||||
$eventName = $event?->name['de'] ?? $event?->name['en'] ?? $event?->name ?? __('emails.package_limits.event_fallback');
|
||||
$url = url('/tenant/events/'.($event?->slug ?? ''));
|
||||
$subject = __('emails.package_limits.guest_limit.subject', [
|
||||
'event' => $eventName,
|
||||
]);
|
||||
$greeting = __('emails.package_limits.guest_limit.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]);
|
||||
|
||||
return (new MailMessage)
|
||||
->subject(__('emails.package_limits.guest_limit.subject', [
|
||||
'event' => $eventName,
|
||||
]))
|
||||
->greeting(__('emails.package_limits.guest_limit.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]))
|
||||
->line(__('emails.package_limits.guest_limit.body', [
|
||||
'event' => $eventName,
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'limit' => $this->limit,
|
||||
]))
|
||||
->line(__('emails.package_limits.guest_limit.cta_addon'))
|
||||
->action(__('emails.package_limits.guest_limit.addon_action'), $url)
|
||||
->action(__('emails.package_limits.guest_limit.action'), $url)
|
||||
->line(__('emails.package_limits.footer'));
|
||||
->subject($subject)
|
||||
->view('emails.notifications.basic', [
|
||||
'title' => $subject,
|
||||
'preheader' => $greeting,
|
||||
'heroTitle' => $greeting,
|
||||
'heroSubtitle' => $subject,
|
||||
'lines' => [
|
||||
__('emails.package_limits.guest_limit.body', [
|
||||
'event' => $eventName,
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'limit' => $this->limit,
|
||||
]),
|
||||
__('emails.package_limits.guest_limit.cta_addon'),
|
||||
],
|
||||
'cta' => [
|
||||
[
|
||||
'label' => __('emails.package_limits.guest_limit.addon_action'),
|
||||
'url' => $url,
|
||||
],
|
||||
[
|
||||
'label' => __('emails.package_limits.guest_limit.action'),
|
||||
'url' => $url,
|
||||
],
|
||||
],
|
||||
'footer' => __('emails.package_limits.footer'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,24 +34,38 @@ class EventPackageGuestThresholdNotification extends Notification implements Sho
|
||||
$remaining = max(0, $this->limit - $this->used);
|
||||
$eventName = $event?->name['de'] ?? $event?->name['en'] ?? $event?->name ?? __('emails.package_limits.event_fallback');
|
||||
$url = url('/tenant/events/'.($event?->slug ?? ''));
|
||||
$subject = __('emails.package_limits.guest_threshold.subject', [
|
||||
'event' => $eventName,
|
||||
'percentage' => $percentage,
|
||||
]);
|
||||
$greeting = __('emails.package_limits.guest_threshold.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]);
|
||||
|
||||
return (new MailMessage)
|
||||
->subject(__('emails.package_limits.guest_threshold.subject', [
|
||||
'event' => $eventName,
|
||||
'percentage' => $percentage,
|
||||
]))
|
||||
->greeting(__('emails.package_limits.guest_threshold.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]))
|
||||
->line(__('emails.package_limits.guest_threshold.body', [
|
||||
'event' => $eventName,
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'percentage' => $percentage,
|
||||
'used' => $this->used,
|
||||
'limit' => $this->limit,
|
||||
'remaining' => $remaining,
|
||||
]))
|
||||
->action(__('emails.package_limits.guest_threshold.action'), $url)
|
||||
->line(__('emails.package_limits.footer'));
|
||||
->subject($subject)
|
||||
->view('emails.notifications.basic', [
|
||||
'title' => $subject,
|
||||
'preheader' => $greeting,
|
||||
'heroTitle' => $greeting,
|
||||
'heroSubtitle' => $subject,
|
||||
'lines' => [
|
||||
__('emails.package_limits.guest_threshold.body', [
|
||||
'event' => $eventName,
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'percentage' => $percentage,
|
||||
'used' => $this->used,
|
||||
'limit' => $this->limit,
|
||||
'remaining' => $remaining,
|
||||
]),
|
||||
],
|
||||
'cta' => [
|
||||
[
|
||||
'label' => __('emails.package_limits.guest_threshold.action'),
|
||||
'url' => $url,
|
||||
],
|
||||
],
|
||||
'footer' => __('emails.package_limits.footer'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,22 +30,39 @@ class EventPackagePhotoLimitNotification extends Notification implements ShouldQ
|
||||
|
||||
$eventName = $event?->name['de'] ?? $event?->name['en'] ?? $event?->name ?? __('emails.package_limits.event_fallback');
|
||||
$url = url('/tenant/events/'.($event?->slug ?? ''));
|
||||
$subject = __('emails.package_limits.photo_limit.subject', [
|
||||
'event' => $eventName,
|
||||
]);
|
||||
$greeting = __('emails.package_limits.photo_limit.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]);
|
||||
|
||||
return (new MailMessage)
|
||||
->subject(__('emails.package_limits.photo_limit.subject', [
|
||||
'event' => $eventName,
|
||||
]))
|
||||
->greeting(__('emails.package_limits.photo_limit.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]))
|
||||
->line(__('emails.package_limits.photo_limit.body', [
|
||||
'event' => $eventName,
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'limit' => $this->limit,
|
||||
]))
|
||||
->line(__('emails.package_limits.photo_limit.cta_addon'))
|
||||
->action(__('emails.package_limits.photo_limit.addon_action'), $url)
|
||||
->action(__('emails.package_limits.photo_limit.action'), $url)
|
||||
->line(__('emails.package_limits.footer'));
|
||||
->subject($subject)
|
||||
->view('emails.notifications.basic', [
|
||||
'title' => $subject,
|
||||
'preheader' => $greeting,
|
||||
'heroTitle' => $greeting,
|
||||
'heroSubtitle' => $subject,
|
||||
'lines' => [
|
||||
__('emails.package_limits.photo_limit.body', [
|
||||
'event' => $eventName,
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'limit' => $this->limit,
|
||||
]),
|
||||
__('emails.package_limits.photo_limit.cta_addon'),
|
||||
],
|
||||
'cta' => [
|
||||
[
|
||||
'label' => __('emails.package_limits.photo_limit.addon_action'),
|
||||
'url' => $url,
|
||||
],
|
||||
[
|
||||
'label' => __('emails.package_limits.photo_limit.action'),
|
||||
'url' => $url,
|
||||
],
|
||||
],
|
||||
'footer' => __('emails.package_limits.footer'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,24 +35,38 @@ class EventPackagePhotoThresholdNotification extends Notification implements Sho
|
||||
$eventName = $event?->name['de'] ?? $event?->name['en'] ?? $event?->name ?? __('emails.package_limits.event_fallback');
|
||||
|
||||
$url = url('/tenant/events/'.($event?->slug ?? ''));
|
||||
$subject = __('emails.package_limits.photo_threshold.subject', [
|
||||
'event' => $eventName,
|
||||
'percentage' => $percentage,
|
||||
]);
|
||||
$greeting = __('emails.package_limits.photo_threshold.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]);
|
||||
|
||||
return (new MailMessage)
|
||||
->subject(__('emails.package_limits.photo_threshold.subject', [
|
||||
'event' => $eventName,
|
||||
'percentage' => $percentage,
|
||||
]))
|
||||
->greeting(__('emails.package_limits.photo_threshold.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]))
|
||||
->line(__('emails.package_limits.photo_threshold.body', [
|
||||
'event' => $eventName,
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'percentage' => $percentage,
|
||||
'used' => $this->used,
|
||||
'limit' => $this->limit,
|
||||
'remaining' => $remaining,
|
||||
]))
|
||||
->action(__('emails.package_limits.photo_threshold.action'), $url)
|
||||
->line(__('emails.package_limits.footer'));
|
||||
->subject($subject)
|
||||
->view('emails.notifications.basic', [
|
||||
'title' => $subject,
|
||||
'preheader' => $greeting,
|
||||
'heroTitle' => $greeting,
|
||||
'heroSubtitle' => $subject,
|
||||
'lines' => [
|
||||
__('emails.package_limits.photo_threshold.body', [
|
||||
'event' => $eventName,
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'percentage' => $percentage,
|
||||
'used' => $this->used,
|
||||
'limit' => $this->limit,
|
||||
'remaining' => $remaining,
|
||||
]),
|
||||
],
|
||||
'cta' => [
|
||||
[
|
||||
'label' => __('emails.package_limits.photo_threshold.action'),
|
||||
'url' => $url,
|
||||
],
|
||||
],
|
||||
'footer' => __('emails.package_limits.footer'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,19 +28,33 @@ class TenantPackageEventLimitNotification extends Notification implements Should
|
||||
$package = $this->tenantPackage->package;
|
||||
|
||||
$url = url('/tenant/billing');
|
||||
$subject = __('emails.package_limits.event_limit.subject', [
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
]);
|
||||
$greeting = __('emails.package_limits.event_limit.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]);
|
||||
|
||||
return (new MailMessage)
|
||||
->subject(__('emails.package_limits.event_limit.subject', [
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
]))
|
||||
->greeting(__('emails.package_limits.event_limit.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]))
|
||||
->line(__('emails.package_limits.event_limit.body', [
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'limit' => $this->limit,
|
||||
]))
|
||||
->action(__('emails.package_limits.event_limit.action'), $url)
|
||||
->line(__('emails.package_limits.footer'));
|
||||
->subject($subject)
|
||||
->view('emails.notifications.basic', [
|
||||
'title' => $subject,
|
||||
'preheader' => $greeting,
|
||||
'heroTitle' => $greeting,
|
||||
'heroSubtitle' => $subject,
|
||||
'lines' => [
|
||||
__('emails.package_limits.event_limit.body', [
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'limit' => $this->limit,
|
||||
]),
|
||||
],
|
||||
'cta' => [
|
||||
[
|
||||
'label' => __('emails.package_limits.event_limit.action'),
|
||||
'url' => $url,
|
||||
],
|
||||
],
|
||||
'footer' => __('emails.package_limits.footer'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,23 +33,37 @@ class TenantPackageEventThresholdNotification extends Notification implements Sh
|
||||
$remaining = max(0, $this->limit - $this->used);
|
||||
|
||||
$url = url('/tenant/billing');
|
||||
$subject = __('emails.package_limits.event_threshold.subject', [
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'percentage' => $percentage,
|
||||
]);
|
||||
$greeting = __('emails.package_limits.event_threshold.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]);
|
||||
|
||||
return (new MailMessage)
|
||||
->subject(__('emails.package_limits.event_threshold.subject', [
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'percentage' => $percentage,
|
||||
]))
|
||||
->greeting(__('emails.package_limits.event_threshold.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]))
|
||||
->line(__('emails.package_limits.event_threshold.body', [
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'percentage' => $percentage,
|
||||
'used' => $this->used,
|
||||
'limit' => $this->limit,
|
||||
'remaining' => $remaining,
|
||||
]))
|
||||
->action(__('emails.package_limits.event_threshold.action'), $url)
|
||||
->line(__('emails.package_limits.footer'));
|
||||
->subject($subject)
|
||||
->view('emails.notifications.basic', [
|
||||
'title' => $subject,
|
||||
'preheader' => $greeting,
|
||||
'heroTitle' => $greeting,
|
||||
'heroSubtitle' => $subject,
|
||||
'lines' => [
|
||||
__('emails.package_limits.event_threshold.body', [
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'percentage' => $percentage,
|
||||
'used' => $this->used,
|
||||
'limit' => $this->limit,
|
||||
'remaining' => $remaining,
|
||||
]),
|
||||
],
|
||||
'cta' => [
|
||||
[
|
||||
'label' => __('emails.package_limits.event_threshold.action'),
|
||||
'url' => $url,
|
||||
],
|
||||
],
|
||||
'footer' => __('emails.package_limits.footer'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,19 +25,33 @@ class TenantPackageExpiredNotification extends Notification implements ShouldQue
|
||||
$package = $this->tenantPackage->package;
|
||||
|
||||
$url = url('/tenant/billing');
|
||||
$subject = __('emails.package_limits.package_expired.subject', [
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
]);
|
||||
$greeting = __('emails.package_limits.package_expired.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]);
|
||||
|
||||
return (new MailMessage)
|
||||
->subject(__('emails.package_limits.package_expired.subject', [
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
]))
|
||||
->greeting(__('emails.package_limits.package_expired.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]))
|
||||
->line(__('emails.package_limits.package_expired.body', [
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'date' => optional($this->tenantPackage->expires_at)->toFormattedDateString(),
|
||||
]))
|
||||
->action(__('emails.package_limits.package_expired.action'), $url)
|
||||
->line(__('emails.package_limits.footer'));
|
||||
->subject($subject)
|
||||
->view('emails.notifications.basic', [
|
||||
'title' => $subject,
|
||||
'preheader' => $greeting,
|
||||
'heroTitle' => $greeting,
|
||||
'heroSubtitle' => $subject,
|
||||
'lines' => [
|
||||
__('emails.package_limits.package_expired.body', [
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'date' => optional($this->tenantPackage->expires_at)->toFormattedDateString(),
|
||||
]),
|
||||
],
|
||||
'cta' => [
|
||||
[
|
||||
'label' => __('emails.package_limits.package_expired.action'),
|
||||
'url' => $url,
|
||||
],
|
||||
],
|
||||
'footer' => __('emails.package_limits.footer'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,21 +28,35 @@ class TenantPackageExpiringNotification extends Notification implements ShouldQu
|
||||
$package = $this->tenantPackage->package;
|
||||
|
||||
$url = url('/tenant/billing');
|
||||
$subject = trans_choice('emails.package_limits.package_expiring.subject', $this->daysRemaining, [
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'days' => $this->daysRemaining,
|
||||
]);
|
||||
$greeting = __('emails.package_limits.package_expiring.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]);
|
||||
|
||||
return (new MailMessage)
|
||||
->subject(trans_choice('emails.package_limits.package_expiring.subject', $this->daysRemaining, [
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'days' => $this->daysRemaining,
|
||||
]))
|
||||
->greeting(__('emails.package_limits.package_expiring.greeting', [
|
||||
'name' => $tenant?->name ?? __('emails.package_limits.team_fallback'),
|
||||
]))
|
||||
->line(trans_choice('emails.package_limits.package_expiring.body', $this->daysRemaining, [
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'days' => $this->daysRemaining,
|
||||
'date' => optional($this->tenantPackage->expires_at)->toFormattedDateString(),
|
||||
]))
|
||||
->action(__('emails.package_limits.package_expiring.action'), $url)
|
||||
->line(__('emails.package_limits.footer'));
|
||||
->subject($subject)
|
||||
->view('emails.notifications.basic', [
|
||||
'title' => $subject,
|
||||
'preheader' => $greeting,
|
||||
'heroTitle' => $greeting,
|
||||
'heroSubtitle' => $subject,
|
||||
'lines' => [
|
||||
trans_choice('emails.package_limits.package_expiring.body', $this->daysRemaining, [
|
||||
'package' => $package?->getNameForLocale() ?? $package?->name ?? __('emails.package_limits.package_fallback'),
|
||||
'days' => $this->daysRemaining,
|
||||
'date' => optional($this->tenantPackage->expires_at)->toFormattedDateString(),
|
||||
]),
|
||||
],
|
||||
'cta' => [
|
||||
[
|
||||
'label' => __('emails.package_limits.package_expiring.action'),
|
||||
'url' => $url,
|
||||
],
|
||||
],
|
||||
'footer' => __('emails.package_limits.footer'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,38 +35,45 @@ class TenantFeedbackSubmitted extends Notification implements ShouldQueue
|
||||
'tenant' => $tenantName,
|
||||
'sentiment' => $sentiment,
|
||||
]);
|
||||
|
||||
$mail = (new MailMessage())
|
||||
->subject($subject)
|
||||
->line(__('emails.tenant_feedback.tenant', ['tenant' => $tenantName]))
|
||||
->line(__('emails.tenant_feedback.category', ['category' => $this->feedback->category ? Str::headline($this->feedback->category) : '—']))
|
||||
->line(__('emails.tenant_feedback.sentiment', ['sentiment' => $sentiment]));
|
||||
$lines = [
|
||||
__('emails.tenant_feedback.tenant', ['tenant' => $tenantName]),
|
||||
__('emails.tenant_feedback.category', ['category' => $this->feedback->category ? Str::headline($this->feedback->category) : '—']),
|
||||
__('emails.tenant_feedback.sentiment', ['sentiment' => $sentiment]),
|
||||
];
|
||||
|
||||
if ($eventName) {
|
||||
$mail->line(__('emails.tenant_feedback.event', ['event' => $eventName]));
|
||||
$lines[] = __('emails.tenant_feedback.event', ['event' => $eventName]);
|
||||
}
|
||||
|
||||
if ($rating) {
|
||||
$mail->line(__('emails.tenant_feedback.rating', ['rating' => $rating]));
|
||||
$lines[] = __('emails.tenant_feedback.rating', ['rating' => $rating]);
|
||||
}
|
||||
|
||||
if ($this->feedback->title) {
|
||||
$mail->line(__('emails.tenant_feedback.title', ['subject' => $this->feedback->title]));
|
||||
$lines[] = __('emails.tenant_feedback.title', ['subject' => $this->feedback->title]);
|
||||
}
|
||||
|
||||
if ($this->feedback->message) {
|
||||
$mail->line(__('emails.tenant_feedback.message'))->line($this->feedback->message);
|
||||
$lines[] = __('emails.tenant_feedback.message');
|
||||
$lines[] = $this->feedback->message;
|
||||
}
|
||||
|
||||
$url = TenantFeedbackResource::getUrl('view', ['record' => $this->feedback], panel: 'superadmin');
|
||||
|
||||
if ($url) {
|
||||
$mail->action(__('emails.tenant_feedback.open'), $url);
|
||||
}
|
||||
$lines[] = __('emails.tenant_feedback.received_at', ['date' => $this->feedback->created_at?->toDayDateTimeString()]);
|
||||
|
||||
$mail->line(__('emails.tenant_feedback.received_at', ['date' => $this->feedback->created_at?->toDayDateTimeString()]));
|
||||
|
||||
return $mail;
|
||||
return (new MailMessage)
|
||||
->subject($subject)
|
||||
->view('emails.notifications.basic', [
|
||||
'title' => $subject,
|
||||
'preheader' => $subject,
|
||||
'heroTitle' => $subject,
|
||||
'lines' => $lines,
|
||||
'cta' => $url ? [[
|
||||
'label' => __('emails.tenant_feedback.open'),
|
||||
'url' => $url,
|
||||
]] : [],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function resolveName(mixed $name): ?string
|
||||
|
||||
@@ -11,9 +11,7 @@ class UploadPipelineFailed extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct(private readonly array $context)
|
||||
{
|
||||
}
|
||||
public function __construct(private readonly array $context) {}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
@@ -29,15 +27,29 @@ class UploadPipelineFailed extends Notification implements ShouldQueue
|
||||
public function toMail(object $notifiable): MailMessage
|
||||
{
|
||||
$context = $this->context;
|
||||
$job = $context['job'] ?? 'n/a';
|
||||
$queue = $context['queue'] ?? 'n/a';
|
||||
$eventId = $context['event_id'] ?? 'n/a';
|
||||
$photoId = $context['photo_id'] ?? 'n/a';
|
||||
$exception = $context['exception'] ?? 'n/a';
|
||||
$timestamp = now()->toDateTimeString();
|
||||
|
||||
return (new MailMessage)
|
||||
->subject('Upload-Pipeline Fehler: '.($context['job'] ?? 'Unbekannter Job'))
|
||||
->line('In der Upload-Pipeline ist ein Fehler aufgetreten.')
|
||||
->line('Job: '.($context['job'] ?? 'n/a'))
|
||||
->line('Queue: '.($context['queue'] ?? 'n/a'))
|
||||
->line('Event ID: '.($context['event_id'] ?? 'n/a'))
|
||||
->line('Foto ID: '.($context['photo_id'] ?? 'n/a'))
|
||||
->line('Exception: '.($context['exception'] ?? 'n/a'))
|
||||
->line('Zeitpunkt: '.now()->toDateTimeString());
|
||||
->subject(__('emails.upload_pipeline_failed.subject', ['job' => $job]))
|
||||
->view('emails.notifications.basic', [
|
||||
'title' => __('emails.upload_pipeline_failed.subject', ['job' => $job]),
|
||||
'preheader' => __('emails.upload_pipeline_failed.preheader'),
|
||||
'heroTitle' => __('emails.upload_pipeline_failed.hero_title'),
|
||||
'heroSubtitle' => __('emails.upload_pipeline_failed.hero_subtitle'),
|
||||
'lines' => [
|
||||
__('emails.upload_pipeline_failed.line_job', ['job' => $job]),
|
||||
__('emails.upload_pipeline_failed.line_queue', ['queue' => $queue]),
|
||||
__('emails.upload_pipeline_failed.line_event', ['event' => $eventId]),
|
||||
__('emails.upload_pipeline_failed.line_photo', ['photo' => $photoId]),
|
||||
__('emails.upload_pipeline_failed.line_exception', ['exception' => $exception]),
|
||||
__('emails.upload_pipeline_failed.line_time', ['time' => $timestamp]),
|
||||
],
|
||||
'footer' => __('emails.upload_pipeline_failed.footer'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
22
app/Notifications/VerifyEmailNotification.php
Normal file
22
app/Notifications/VerifyEmailNotification.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use Illuminate\Auth\Notifications\VerifyEmail;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class VerifyEmailNotification extends VerifyEmail
|
||||
{
|
||||
public function toMail($notifiable): MailMessage
|
||||
{
|
||||
$verificationUrl = $this->verificationUrl($notifiable);
|
||||
|
||||
return (new MailMessage)
|
||||
->subject(__('emails.verification.subject'))
|
||||
->view('emails.verify-email', [
|
||||
'user' => $notifiable,
|
||||
'verificationUrl' => $verificationUrl,
|
||||
'expiresIn' => (int) config('auth.verification.expire', 60),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,51 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'brand' => [
|
||||
'label' => 'Die Fotospiel.App',
|
||||
'footer' => 'Mit freundlichen Grüßen,<br>Das Team von Die Fotospiel.App',
|
||||
'tagline' => 'Die Fotospiel.App · Event-Pakete mit Wow-Effekt',
|
||||
],
|
||||
|
||||
'welcome' => [
|
||||
'subject' => 'Willkommen bei Fotospiel, :name!',
|
||||
'greeting' => 'Willkommen bei Fotospiel, :name!',
|
||||
'body' => 'Vielen Dank für Ihre Registrierung. Ihr Account ist nun aktiv.',
|
||||
'subject' => 'Willkommen bei Die Fotospiel.App, :name!',
|
||||
'greeting' => 'Willkommen bei Die Fotospiel.App, :name!',
|
||||
'subtitle' => 'Schön, dass Sie da sind. Ihr Event-Erlebnis kann sofort starten.',
|
||||
'body' => 'Vielen Dank für Ihre Registrierung. Wir haben Ihren Account vorbereitet – jetzt fehlt nur noch Ihr erstes Event.',
|
||||
'account_label' => 'Ihre Zugangsdaten',
|
||||
'username' => 'Benutzername: :username',
|
||||
'email' => 'E-Mail: :email',
|
||||
'verification' => 'Bitte verifizieren Sie Ihre E-Mail-Adresse, um auf das Admin-Panel zuzugreifen.',
|
||||
'footer' => 'Mit freundlichen Grüßen,<br>Das Fotospiel-Team',
|
||||
'verification' => 'Bitte bestätigen Sie Ihre E-Mail-Adresse, um vollen Zugriff zu erhalten.',
|
||||
'next_steps_title' => 'So geht es weiter',
|
||||
'step_one' => 'Event anlegen und das passende Paket auswählen',
|
||||
'step_two' => 'Gäste einladen und den Upload-Link teilen',
|
||||
'step_three' => 'Fotos sammeln, kuratieren und genießen',
|
||||
'cta' => 'Event-Admin öffnen',
|
||||
'footer' => 'Sie brauchen Unterstützung? Wir sind jederzeit für Sie da.',
|
||||
],
|
||||
'verification' => [
|
||||
'subject' => 'Bitte bestätigen Sie Ihre E-Mail-Adresse',
|
||||
'preheader' => 'Bestätigen Sie Ihre E-Mail, um den vollen Zugriff zu erhalten.',
|
||||
'hero_title' => 'E-Mail bestätigen, :name',
|
||||
'hero_subtitle' => 'Ein Klick genügt, um Ihr Konto zu aktivieren.',
|
||||
'body' => 'Bitte bestätigen Sie Ihre E-Mail-Adresse, indem Sie auf den Button klicken.',
|
||||
'expires' => 'Dieser Bestätigungslink ist :minutes Minuten gültig.',
|
||||
'link_fallback' => 'Falls der Button nicht funktioniert, kopieren Sie diesen Link in Ihren Browser:',
|
||||
'cta' => 'E-Mail bestätigen',
|
||||
'footer' => 'Falls Sie kein Konto erstellt haben, können Sie diese E-Mail ignorieren.',
|
||||
],
|
||||
'upload_pipeline_failed' => [
|
||||
'subject' => 'Upload-Pipeline Fehler: :job',
|
||||
'preheader' => 'In der Upload-Pipeline ist ein Fehler aufgetreten.',
|
||||
'hero_title' => 'Upload-Pipeline Alarm',
|
||||
'hero_subtitle' => 'Beim Verarbeiten der Uploads ist ein Fehler aufgetreten.',
|
||||
'line_job' => 'Job: :job',
|
||||
'line_queue' => 'Queue: :queue',
|
||||
'line_event' => 'Event ID: :event',
|
||||
'line_photo' => 'Foto ID: :photo',
|
||||
'line_exception' => 'Exception: :exception',
|
||||
'line_time' => 'Zeitpunkt: :time',
|
||||
'footer' => 'Bitte prüfen Sie die Queue-Logs für weitere Details.',
|
||||
],
|
||||
|
||||
'purchase' => [
|
||||
@@ -17,9 +54,7 @@ return [
|
||||
'package' => 'Package: :package',
|
||||
'price' => 'Preis: :price',
|
||||
'activation' => 'Ihr Event-Paket ist jetzt im Tenant-Account aktiviert.',
|
||||
'footer' => 'Mit freundlichen Grüßen,<br>Das Team von Die Fotospiel.App',
|
||||
'brand_label' => 'Die Fotospiel.App',
|
||||
'brand_footer' => 'Die Fotospiel.App · Event-Pakete mit Wow-Effekt',
|
||||
'footer' => 'Sie brauchen Unterstützung? Wir sind jederzeit für Sie da.',
|
||||
'subtitle' => 'Ihre Bestellung wurde erfolgreich bestätigt. Hier finden Sie alle Details auf einen Blick.',
|
||||
'summary_title' => 'Bestellübersicht',
|
||||
'package_label' => 'Event-Paket',
|
||||
@@ -57,23 +92,20 @@ return [
|
||||
'subject_1w' => 'Letzte Chance: Ihr gespeichertes Paket',
|
||||
|
||||
'greeting' => 'Hallo :name,',
|
||||
|
||||
'body_1h' => 'Sie haben vor kurzem begonnen, unser :package Paket zu kaufen, aber noch nicht abgeschlossen. Ihr Warenkorb ist für Sie reserviert.',
|
||||
|
||||
'body_24h' => 'Erinnerung: Ihr :package Paket wartet seit 24 Stunden auf Sie. Schließen Sie jetzt Ihren Kauf ab und sichern Sie sich alle Vorteile.',
|
||||
|
||||
'body_1w' => 'Letzte Erinnerung: Ihr :package Paket wartet seit einer Woche auf Sie. Dies ist Ihre letzte Chance, den Kauf abzuschließen.',
|
||||
|
||||
'cta_button' => 'Jetzt fortfahren',
|
||||
'subtitle' => ':package ist nur einen Schritt entfernt.',
|
||||
'body_1h' => 'Sie haben vor kurzem unser :package Event-Paket ausgewählt. Ihr Checkout ist weiterhin für Sie reserviert.',
|
||||
'body_24h' => 'Ihr :package Event-Paket wartet seit 24 Stunden auf Sie. Schließen Sie den Kauf jetzt ab und starten Sie direkt.',
|
||||
'body_1w' => 'Letzte Erinnerung: Ihr :package Event-Paket ist noch reserviert. Wenn Sie möchten, können Sie den Checkout jetzt abschließen.',
|
||||
'cta_button' => 'Checkout fortsetzen',
|
||||
'cta_link' => 'Oder kopieren Sie diesen Link: :url',
|
||||
|
||||
'benefits_title' => 'Warum jetzt kaufen?',
|
||||
'benefit1' => 'Schneller Checkout in 2 Minuten',
|
||||
'cta_hint_title' => 'Reserviert für Sie',
|
||||
'cta_hint_body' => 'Ihr Angebot bleibt bestehen – Sie können jederzeit nahtlos fortfahren.',
|
||||
'benefits_title' => 'Was Sie erwartet',
|
||||
'benefit1' => 'Premium Checkout in wenigen Minuten',
|
||||
'benefit2' => 'Sichere Zahlung mit Paddle',
|
||||
'benefit3' => 'Sofortiger Zugriff nach Zahlung',
|
||||
'benefit4' => '10% Rabatt sichern',
|
||||
|
||||
'footer' => 'Mit freundlichen Grüßen,<br>Das Fotospiel-Team',
|
||||
'benefit3' => 'Sofortige Aktivierung nach Zahlung',
|
||||
'benefit4' => 'Support durch das Die Fotospiel.App Team',
|
||||
'footer' => 'Wir helfen Ihnen gern weiter, falls Fragen offen sind.',
|
||||
],
|
||||
|
||||
'contact' => [
|
||||
@@ -82,10 +114,13 @@ return [
|
||||
],
|
||||
|
||||
'contact_confirmation' => [
|
||||
'subject' => 'Vielen Dank für Ihre Nachricht, :name!',
|
||||
'subject' => 'Danke für Ihre Nachricht, :name!',
|
||||
'greeting' => 'Hallo :name,',
|
||||
'body' => 'Vielen Dank für Ihre Nachricht an das Fotospiel-Team. Wir melden uns so schnell wie möglich zurück.',
|
||||
'footer' => 'Viele Grüße<br>Ihr Fotospiel-Team',
|
||||
'subtitle' => 'Wir kümmern uns persönlich um Ihr Anliegen.',
|
||||
'body' => 'Vielen Dank für Ihre Nachricht. Unser Team meldet sich so schnell wie möglich mit einer passenden Lösung.',
|
||||
'response_time' => 'In der Regel erhalten Sie innerhalb eines Werktags eine Antwort.',
|
||||
'cta' => 'Support kontaktieren',
|
||||
'footer' => 'Viele Grüße<br>Ihr Die Fotospiel.App Team',
|
||||
],
|
||||
|
||||
'package_limits' => [
|
||||
@@ -163,8 +198,14 @@ return [
|
||||
'receipt' => [
|
||||
'subject' => 'Add-on gekauft: :addon',
|
||||
'greeting' => 'Hallo :name,',
|
||||
'body' => 'Sie haben „ :addon “ für das Event „ :event “ gebucht. Betrag: :amount.',
|
||||
'summary' => 'Enthalten: +:photos Fotos, +:guests Gäste, +:days Tage Galerie.',
|
||||
'subtitle' => 'Ihr Add-on ist aktiv und sofort verfügbar.',
|
||||
'body' => 'Sie haben „:addon“ für das Event „:event“ gebucht. Gesamtbetrag: :amount.',
|
||||
'summary_title' => 'Enthaltene Upgrades',
|
||||
'summary' => [
|
||||
'photos' => '+:count Fotos',
|
||||
'guests' => '+:count Gäste',
|
||||
'gallery' => '+:count Tage Galerie',
|
||||
],
|
||||
'unknown_amount' => 'k.A.',
|
||||
'action' => 'Event-Dashboard öffnen',
|
||||
],
|
||||
@@ -174,12 +215,14 @@ return [
|
||||
'purchaser' => [
|
||||
'subject' => 'Dein Geschenkgutschein (:amount :currency)',
|
||||
'greeting' => 'Danke für deinen Kauf!',
|
||||
'body' => 'Hier ist dein Fotospiel-Geschenkgutschein im Wert von :amount :currency. Teile den Code mit deiner beschenkten Person: :recipient.',
|
||||
'subtitle' => 'Dein Gutschein ist bereit, Freude zu schenken.',
|
||||
'body' => 'Hier ist dein Fotospiel-Geschenkgutschein im Wert von :amount :currency. Teile den Code mit :recipient und schenke ein unvergessliches Event.',
|
||||
'recipient_fallback' => 'dein:e Beschenkte:r',
|
||||
],
|
||||
'recipient' => [
|
||||
'subject' => 'Du hast einen Fotospiel-Geschenkgutschein erhalten (:amount :currency)',
|
||||
'greeting' => 'Du hast ein Geschenk bekommen!',
|
||||
'subtitle' => 'Zeit für ein Event mit Wow-Effekt.',
|
||||
'body' => ':purchaser hat dir einen Fotospiel-Geschenkgutschein im Wert von :amount :currency gesendet. Löse ihn mit dem untenstehenden Code ein.',
|
||||
],
|
||||
'code_label' => 'Gutscheincode',
|
||||
@@ -187,7 +230,7 @@ return [
|
||||
'expiry' => 'Gültig bis :date.',
|
||||
'message_title' => 'Persönliche Nachricht',
|
||||
'withdrawal' => 'Widerrufsbelehrung: <a href=":url">Details ansehen</a> (14 Tage; erlischt mit Einlösung).',
|
||||
'footer' => 'Viele Grüße,<br>dein Fotospiel Team',
|
||||
'footer' => 'Viele Grüße,<br>dein Die Fotospiel.App Team',
|
||||
'printable' => 'Druckversion (mit QR)',
|
||||
'reminder' => 'Erinnerung: Dein Gutschein ist noch nicht eingelöst.',
|
||||
'expiry_soon' => 'Hinweis: Dein Gutschein läuft bald ab.',
|
||||
|
||||
@@ -1,14 +1,50 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'brand' => [
|
||||
'label' => 'Die Fotospiel.App',
|
||||
'footer' => 'Best regards,<br>The team at Die Fotospiel.App',
|
||||
'tagline' => 'Die Fotospiel.App · Event packages with wow-factor',
|
||||
],
|
||||
'welcome' => [
|
||||
'subject' => 'Welcome to Fotospiel, :name!',
|
||||
'greeting' => 'Welcome to Fotospiel, :name!',
|
||||
'body' => 'Thank you for your registration. Your account is now active.',
|
||||
'subject' => 'Welcome to Die Fotospiel.App, :name!',
|
||||
'greeting' => 'Welcome to Die Fotospiel.App, :name!',
|
||||
'subtitle' => 'We are glad you are here. Your event experience can start immediately.',
|
||||
'body' => 'Thank you for signing up. Your account is ready—now let’s launch your first event.',
|
||||
'account_label' => 'Your account details',
|
||||
'username' => 'Username: :username',
|
||||
'email' => 'Email: :email',
|
||||
'verification' => 'Please verify your email address to access the admin panel.',
|
||||
'footer' => 'Best regards,<br>The Fotospiel Team',
|
||||
'verification' => 'Please verify your email address to unlock full access.',
|
||||
'next_steps_title' => 'Next steps',
|
||||
'step_one' => 'Create your event and pick the right package',
|
||||
'step_two' => 'Invite guests and share the upload link',
|
||||
'step_three' => 'Collect, curate, and celebrate every photo',
|
||||
'cta' => 'Open Event Admin',
|
||||
'footer' => 'Need help? We are here whenever you need us.',
|
||||
],
|
||||
'verification' => [
|
||||
'subject' => 'Verify your email address',
|
||||
'preheader' => 'Confirm your email to unlock full access.',
|
||||
'hero_title' => 'Confirm your email, :name',
|
||||
'hero_subtitle' => 'One click to activate your account.',
|
||||
'body' => 'Please confirm your email address by clicking the button below.',
|
||||
'expires' => 'This verification link expires in :minutes minutes.',
|
||||
'link_fallback' => 'If the button does not work, copy and paste this link into your browser:',
|
||||
'cta' => 'Verify email',
|
||||
'footer' => 'If you did not create an account, you can ignore this email.',
|
||||
],
|
||||
'upload_pipeline_failed' => [
|
||||
'subject' => 'Upload pipeline error: :job',
|
||||
'preheader' => 'An error occurred in the upload pipeline.',
|
||||
'hero_title' => 'Upload pipeline alert',
|
||||
'hero_subtitle' => 'We hit an error while processing uploads.',
|
||||
'line_job' => 'Job: :job',
|
||||
'line_queue' => 'Queue: :queue',
|
||||
'line_event' => 'Event ID: :event',
|
||||
'line_photo' => 'Photo ID: :photo',
|
||||
'line_exception' => 'Exception: :exception',
|
||||
'line_time' => 'Time: :time',
|
||||
'footer' => 'Please investigate the failure in the queue logs.',
|
||||
],
|
||||
|
||||
'purchase' => [
|
||||
@@ -17,9 +53,7 @@ return [
|
||||
'package' => 'Package: :package',
|
||||
'price' => 'Price: :price',
|
||||
'activation' => 'Your event package is now activated in your tenant account.',
|
||||
'footer' => 'Best regards,<br>The team at Die Fotospiel.App',
|
||||
'brand_label' => 'Die Fotospiel.App',
|
||||
'brand_footer' => 'Die Fotospiel.App · Event packages with wow-factor',
|
||||
'footer' => 'Need assistance? We are always happy to help.',
|
||||
'subtitle' => 'Your order has been confirmed successfully. Here are the details at a glance.',
|
||||
'summary_title' => 'Order summary',
|
||||
'package_label' => 'Event package',
|
||||
@@ -57,23 +91,20 @@ return [
|
||||
'subject_1w' => 'Last Chance: Your Saved Package',
|
||||
|
||||
'greeting' => 'Hello :name,',
|
||||
|
||||
'body_1h' => 'You recently started purchasing our :package package but haven\'t completed it yet. Your cart is reserved for you.',
|
||||
|
||||
'body_24h' => 'Reminder: Your :package package has been waiting for 24 hours. Complete your purchase now and secure all the benefits.',
|
||||
|
||||
'body_1w' => 'Final reminder: Your :package package has been waiting for a week. This is your last chance to complete the purchase.',
|
||||
|
||||
'cta_button' => 'Continue Now',
|
||||
'subtitle' => ':package is just one step away.',
|
||||
'body_1h' => 'You selected the :package event package but haven\'t completed checkout yet. Your selection is still reserved.',
|
||||
'body_24h' => 'Your :package event package has been waiting for 24 hours. Finish checkout now and get started instantly.',
|
||||
'body_1w' => 'Final reminder: Your :package event package is still reserved. You can complete your purchase at any time.',
|
||||
'cta_button' => 'Resume checkout',
|
||||
'cta_link' => 'Or copy this link: :url',
|
||||
|
||||
'benefits_title' => 'Why buy now?',
|
||||
'benefit1' => 'Quick checkout in 2 minutes',
|
||||
'cta_hint_title' => 'Reserved for you',
|
||||
'cta_hint_body' => 'Your selection stays locked—continue whenever you are ready.',
|
||||
'benefits_title' => 'What you get',
|
||||
'benefit1' => 'Premium checkout in minutes',
|
||||
'benefit2' => 'Secure payment with Paddle',
|
||||
'benefit3' => 'Instant access after payment',
|
||||
'benefit4' => 'Secure 10% discount',
|
||||
|
||||
'footer' => 'Best regards,<br>The Fotospiel Team',
|
||||
'benefit3' => 'Instant activation after payment',
|
||||
'benefit4' => 'Support from the Die Fotospiel.App team',
|
||||
'footer' => 'Let us know if you need anything.',
|
||||
],
|
||||
|
||||
'contact' => [
|
||||
@@ -82,10 +113,13 @@ return [
|
||||
],
|
||||
|
||||
'contact_confirmation' => [
|
||||
'subject' => 'Thank you for reaching out, :name!',
|
||||
'subject' => 'Thanks for reaching out, :name!',
|
||||
'greeting' => 'Hi :name,',
|
||||
'body' => 'Thank you for your message to the Fotospiel team. We will get back to you as soon as possible.',
|
||||
'footer' => 'Best regards,<br>The Fotospiel Team',
|
||||
'subtitle' => 'Your message is in good hands.',
|
||||
'body' => 'Thank you for contacting us. Our team will reply with a tailored answer as quickly as possible.',
|
||||
'response_time' => 'We usually respond within one business day.',
|
||||
'cta' => 'Contact support',
|
||||
'footer' => 'Best regards,<br>The Die Fotospiel.App team',
|
||||
],
|
||||
|
||||
'package_limits' => [
|
||||
@@ -163,8 +197,14 @@ return [
|
||||
'receipt' => [
|
||||
'subject' => 'Add-on purchase: :addon',
|
||||
'greeting' => 'Hello :name,',
|
||||
'body' => 'You purchased " :addon " for the event " :event ". Amount: :amount.',
|
||||
'summary' => 'Included: +:photos photos, +:guests guests, +:days gallery days.',
|
||||
'subtitle' => 'Your add-on is active and ready to use.',
|
||||
'body' => 'You booked “:addon” for the event “:event”. Total: :amount.',
|
||||
'summary_title' => 'Included upgrades',
|
||||
'summary' => [
|
||||
'photos' => '+:count photos',
|
||||
'guests' => '+:count guests',
|
||||
'gallery' => '+:count gallery days',
|
||||
],
|
||||
'unknown_amount' => 'n/a',
|
||||
'action' => 'Open event dashboard',
|
||||
],
|
||||
@@ -233,12 +273,14 @@ return [
|
||||
'purchaser' => [
|
||||
'subject' => 'Your gift voucher (:amount :currency)',
|
||||
'greeting' => 'Thank you for your purchase!',
|
||||
'body' => 'Here is your Fotospiel gift voucher worth :amount :currency. You can share the code with your recipient: :recipient.',
|
||||
'subtitle' => 'Your voucher is ready to make someone smile.',
|
||||
'body' => 'Here is your Fotospiel gift voucher worth :amount :currency. Share the code with :recipient and gift an unforgettable event.',
|
||||
'recipient_fallback' => 'your recipient',
|
||||
],
|
||||
'recipient' => [
|
||||
'subject' => 'You received a Fotospiel gift voucher (:amount :currency)',
|
||||
'greeting' => 'You have a gift!',
|
||||
'subtitle' => 'Time for an event with wow-factor.',
|
||||
'body' => ':purchaser sent you a Fotospiel gift voucher worth :amount :currency. Redeem it with the code below.',
|
||||
],
|
||||
'code_label' => 'Voucher code',
|
||||
@@ -246,7 +288,7 @@ return [
|
||||
'expiry' => 'Valid until :date.',
|
||||
'message_title' => 'Personal message',
|
||||
'withdrawal' => 'Withdrawal policy: <a href=":url">View details</a> (14 days; expires upon redemption).',
|
||||
'footer' => 'Best regards,<br>The Fotospiel Team',
|
||||
'footer' => 'Best regards,<br>The Die Fotospiel.App team',
|
||||
'printable' => 'Printable version (with QR)',
|
||||
'reminder' => 'Reminder: You still have an unused voucher.',
|
||||
'expiry_soon' => 'Heads up: Your voucher will expire soon.',
|
||||
|
||||
@@ -1,47 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ __('emails.abandoned_checkout.subject_' . $timing, ['package' => $packageName]) }}</title>
|
||||
<style>
|
||||
.cta-button {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
padding: 12px 24px;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.benefits {
|
||||
background-color: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
.benefit-item {
|
||||
margin: 5px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{ __('emails.abandoned_checkout.greeting', ['name' => $user->fullName]) }}</h1>
|
||||
@extends('emails.partials.layout')
|
||||
|
||||
<p>{{ __('emails.abandoned_checkout.body_' . $timing, ['package' => $packageName]) }}</p>
|
||||
@section('title', __('emails.abandoned_checkout.subject_' . $timing, ['package' => $packageName]))
|
||||
@section('preheader', __('emails.abandoned_checkout.subtitle', ['package' => $packageName]))
|
||||
@section('hero_title', __('emails.abandoned_checkout.greeting', ['name' => $user->fullName]))
|
||||
@section('hero_subtitle', __('emails.abandoned_checkout.subtitle', ['package' => $packageName]))
|
||||
|
||||
<a href="{{ $resumeUrl }}" class="cta-button">
|
||||
@section('content')
|
||||
<p style="margin:0 0 16px; font-size:15px; color:#1f2937;">
|
||||
{{ __('emails.abandoned_checkout.body_' . $timing, ['package' => $packageName]) }}
|
||||
</p>
|
||||
<div style="background-color:#f8fafc; border:1px solid #e2e8f0; border-radius:12px; padding:16px; margin-bottom:16px;">
|
||||
<p style="margin:0 0 6px; font-size:13px; text-transform:uppercase; letter-spacing:0.08em; color:#64748b;">
|
||||
{{ __('emails.abandoned_checkout.cta_hint_title') }}
|
||||
</p>
|
||||
<p style="margin:0; font-size:14px; color:#0f172a;">
|
||||
{{ __('emails.abandoned_checkout.cta_hint_body') }}
|
||||
</p>
|
||||
</div>
|
||||
<p style="margin:0 0 16px; font-size:14px; color:#6b7280;">
|
||||
{{ __('emails.abandoned_checkout.cta_link', ['url' => $resumeUrl]) }}
|
||||
</p>
|
||||
<h3 style="margin:0 0 10px; font-size:16px;">{{ __('emails.abandoned_checkout.benefits_title') }}</h3>
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="border-collapse:collapse;">
|
||||
<tr>
|
||||
<td style="padding:6px 0; font-size:14px; color:#0f172a;">✓ {{ __('emails.abandoned_checkout.benefit1') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:6px 0; font-size:14px; color:#0f172a;">✓ {{ __('emails.abandoned_checkout.benefit2') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:6px 0; font-size:14px; color:#0f172a;">✓ {{ __('emails.abandoned_checkout.benefit3') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:6px 0; font-size:14px; color:#0f172a;">✓ {{ __('emails.abandoned_checkout.benefit4') }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
@endsection
|
||||
|
||||
@section('cta')
|
||||
<a href="{{ $resumeUrl }}" style="display:inline-block; background-color:#111827; color:#ffffff; text-decoration:none; padding:12px 20px; border-radius:999px; font-weight:600; font-size:14px;">
|
||||
{{ __('emails.abandoned_checkout.cta_button') }}
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
<p>{{ __('emails.abandoned_checkout.cta_link', ['url' => $resumeUrl]) }}</p>
|
||||
|
||||
<div class="benefits">
|
||||
<h3>{{ __('emails.abandoned_checkout.benefits_title') }}</h3>
|
||||
<div class="benefit-item">✓ {{ __('emails.abandoned_checkout.benefit1') }}</div>
|
||||
<div class="benefit-item">✓ {{ __('emails.abandoned_checkout.benefit2') }}</div>
|
||||
<div class="benefit-item">✓ {{ __('emails.abandoned_checkout.benefit3') }}</div>
|
||||
<div class="benefit-item">✓ {{ __('emails.abandoned_checkout.benefit4') }}</div>
|
||||
</div>
|
||||
|
||||
<p>{!! __('emails.abandoned_checkout.footer') !!}</p>
|
||||
</body>
|
||||
</html>
|
||||
@section('footer')
|
||||
{!! __('emails.abandoned_checkout.footer') !!}
|
||||
@endsection
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@extends('emails.partials.layout')
|
||||
|
||||
@php
|
||||
/** @var \App\Models\EventPackageAddon $addon */
|
||||
$event = $addon->event;
|
||||
@@ -14,31 +16,45 @@
|
||||
if ($addon->extra_gallery_days > 0) {
|
||||
$summary[] = __('emails.addons.receipt.summary.gallery', ['count' => $addon->extra_gallery_days]);
|
||||
}
|
||||
$eventName = $event?->name['de'] ?? $event?->name['en'] ?? $event?->name ?? __('emails.package_limits.event_fallback');
|
||||
$ctaUrl = url('/tenant/events/'.($event?->slug ?? ''));
|
||||
@endphp
|
||||
|
||||
@component('mail::message')
|
||||
# {{ __('emails.addons.receipt.subject', ['addon' => $label]) }}
|
||||
@section('title', __('emails.addons.receipt.subject', ['addon' => $label]))
|
||||
@section('preheader', __('emails.addons.receipt.subtitle', ['addon' => $label]))
|
||||
@section('hero_title', __('emails.addons.receipt.greeting', ['name' => $tenant?->name ?? __('emails.package_limits.team_fallback')]))
|
||||
@section('hero_subtitle', __('emails.addons.receipt.subtitle', ['addon' => $label]))
|
||||
|
||||
{{ __('emails.addons.receipt.greeting', ['name' => $tenant?->name ?? __('emails.package_limits.team_fallback')]) }}
|
||||
@section('content')
|
||||
<p style="margin:0 0 12px; font-size:15px; color:#1f2937;">
|
||||
{{ __('emails.addons.receipt.body', [
|
||||
'addon' => $label,
|
||||
'event' => $eventName,
|
||||
'amount' => $amount,
|
||||
]) }}
|
||||
</p>
|
||||
@if (! empty($summary))
|
||||
<div style="background-color:#f8fafc; border:1px solid #e2e8f0; border-radius:12px; padding:16px; margin-bottom:12px;">
|
||||
<p style="margin:0 0 8px; font-size:13px; text-transform:uppercase; letter-spacing:0.08em; color:#64748b;">
|
||||
{{ __('emails.addons.receipt.summary_title') }}
|
||||
</p>
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="border-collapse:collapse;">
|
||||
@foreach ($summary as $line)
|
||||
<tr>
|
||||
<td style="padding:6px 0; font-size:14px; color:#0f172a;">{{ $line }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
@endsection
|
||||
|
||||
{{ __('emails.addons.receipt.body', [
|
||||
'addon' => $label,
|
||||
'event' => $event?->name['de'] ?? $event?->name['en'] ?? $event?->name ?? __('emails.package_limits.event_fallback'),
|
||||
'amount' => $amount,
|
||||
]) }}
|
||||
@section('cta')
|
||||
<a href="{{ $ctaUrl }}" style="display:inline-block; background-color:#111827; color:#ffffff; text-decoration:none; padding:12px 20px; border-radius:999px; font-weight:600; font-size:14px;">
|
||||
{{ __('emails.addons.receipt.action') }}
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
@if(!empty($summary))
|
||||
**{{ __('emails.addons.receipt.summary_title', 'Included:') }}**
|
||||
|
||||
@foreach($summary as $line)
|
||||
- {{ $line }}
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
@component('mail::button', ['url' => url('/tenant/events/'.($event?->slug ?? ''))])
|
||||
{{ __('emails.addons.receipt.action') }}
|
||||
@endcomponent
|
||||
|
||||
{{ __('emails.package_limits.footer') }}
|
||||
|
||||
@endcomponent
|
||||
@section('footer')
|
||||
{!! __('emails.brand.footer') !!}
|
||||
@endsection
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
@component('mail::message')
|
||||
# {{ __('emails.contact_confirmation.greeting', ['name' => $name]) }}
|
||||
@extends('emails.partials.layout')
|
||||
|
||||
{{ __('emails.contact_confirmation.body') }}
|
||||
@php
|
||||
$contactEmail = config('mail.contact_address', config('mail.from.address'));
|
||||
@endphp
|
||||
|
||||
{{ __('emails.contact_confirmation.footer') }}
|
||||
@endcomponent
|
||||
@section('title', __('emails.contact_confirmation.subject', ['name' => $name]))
|
||||
@section('preheader', __('emails.contact_confirmation.subtitle'))
|
||||
@section('hero_title', __('emails.contact_confirmation.greeting', ['name' => $name]))
|
||||
@section('hero_subtitle', __('emails.contact_confirmation.subtitle'))
|
||||
|
||||
@section('content')
|
||||
<p style="margin:0 0 12px; font-size:15px; color:#1f2937;">
|
||||
{{ __('emails.contact_confirmation.body') }}
|
||||
</p>
|
||||
<p style="margin:0; font-size:14px; color:#6b7280;">
|
||||
{{ __('emails.contact_confirmation.response_time') }}
|
||||
</p>
|
||||
@endsection
|
||||
|
||||
@section('cta')
|
||||
<a href="mailto:{{ $contactEmail }}" style="display:inline-block; background-color:#111827; color:#ffffff; text-decoration:none; padding:12px 20px; border-radius:999px; font-weight:600; font-size:14px;">
|
||||
{{ __('emails.contact_confirmation.cta') }}
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
@section('footer')
|
||||
{!! __('emails.contact_confirmation.footer') !!}
|
||||
@endsection
|
||||
|
||||
@@ -1,41 +1,48 @@
|
||||
@extends('emails.partials.layout')
|
||||
|
||||
@php
|
||||
$withdrawalUrl = app()->getLocale() === 'de' ? url('/de/widerrufsbelehrung') : url('/en/withdrawal');
|
||||
$subject = $forRecipient
|
||||
? __('emails.gift_voucher.recipient.subject', ['amount' => $amount, 'currency' => $currency])
|
||||
: __('emails.gift_voucher.purchaser.subject', ['amount' => $amount, 'currency' => $currency]);
|
||||
$greeting = $forRecipient
|
||||
? __('emails.gift_voucher.recipient.greeting')
|
||||
: __('emails.gift_voucher.purchaser.greeting');
|
||||
$subtitle = $forRecipient
|
||||
? __('emails.gift_voucher.recipient.subtitle')
|
||||
: __('emails.gift_voucher.purchaser.subtitle');
|
||||
$body = $forRecipient
|
||||
? __('emails.gift_voucher.recipient.body', [
|
||||
'amount' => $amount,
|
||||
'currency' => $currency,
|
||||
'purchaser' => $voucher->purchaser_email,
|
||||
])
|
||||
: __('emails.gift_voucher.purchaser.body', [
|
||||
'amount' => $amount,
|
||||
'currency' => $currency,
|
||||
'recipient' => $voucher->recipient_email ?: __('emails.gift_voucher.purchaser.recipient_fallback'),
|
||||
]);
|
||||
@endphp
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ $forRecipient ? __('emails.gift_voucher.recipient.subject', ['amount' => $amount, 'currency' => $currency]) : __('emails.gift_voucher.purchaser.subject', ['amount' => $amount, 'currency' => $currency]) }}</title>
|
||||
</head>
|
||||
<body style="font-family: Arial, sans-serif; background-color: #f7f7f7; padding: 20px; color: #111827;">
|
||||
<div style="max-width: 640px; margin: 0 auto; background: #ffffff; border-radius: 10px; padding: 28px; box-shadow: 0 10px 30px rgba(0,0,0,0.05);">
|
||||
<h1 style="margin-top: 0; font-size: 22px;">
|
||||
{{ $forRecipient ? __('emails.gift_voucher.recipient.greeting') : __('emails.gift_voucher.purchaser.greeting') }}
|
||||
</h1>
|
||||
<p style="font-size: 15px; line-height: 1.6; margin-bottom: 16px;">
|
||||
{!! $forRecipient
|
||||
? __('emails.gift_voucher.recipient.body', [
|
||||
'amount' => $amount,
|
||||
'currency' => $currency,
|
||||
'purchaser' => $voucher->purchaser_email,
|
||||
])
|
||||
: __('emails.gift_voucher.purchaser.body', [
|
||||
'amount' => $amount,
|
||||
'currency' => $currency,
|
||||
'recipient' => $voucher->recipient_email ?: __('emails.gift_voucher.purchaser.recipient_fallback'),
|
||||
])
|
||||
!!}
|
||||
</p>
|
||||
|
||||
@section('title', $subject)
|
||||
@section('preheader', $subtitle)
|
||||
@section('hero_title', $greeting)
|
||||
@section('hero_subtitle', $subtitle)
|
||||
|
||||
@section('content')
|
||||
<p style="margin:0 0 12px; font-size:15px; color:#1f2937;">
|
||||
{!! $body !!}
|
||||
</p>
|
||||
@if ($voucher->message)
|
||||
<div style="margin: 18px 0; padding: 14px 16px; background: #f3f4f6; border-left: 4px solid #2563eb; border-radius: 8px;">
|
||||
<strong>{{ __('emails.gift_voucher.message_title') }}</strong>
|
||||
<p style="margin: 8px 0 0; white-space: pre-line;">{{ $voucher->message }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div style="margin: 18px 0; padding: 16px; border: 1px dashed #d1d5db; border-radius: 10px; background: #f9fafb;">
|
||||
<p style="margin: 0 0 6px; font-size: 14px; color: #6b7280;">{{ __('emails.gift_voucher.code_label') }}</p>
|
||||
<p style="margin: 0 0 6px; font-size: 13px; text-transform:uppercase; letter-spacing:0.08em; color: #6b7280;">
|
||||
{{ __('emails.gift_voucher.code_label') }}
|
||||
</p>
|
||||
<div style="display: inline-block; padding: 10px 14px; background: #111827; color: #ffffff; border-radius: 8px; font-weight: bold; letter-spacing: 1px;">
|
||||
{{ $voucher->code }}
|
||||
</div>
|
||||
@@ -44,22 +51,18 @@
|
||||
</p>
|
||||
@isset($printUrl)
|
||||
<p style="margin: 8px 0 0; font-size: 14px;">
|
||||
<a href="{{ $printUrl }}">{{ __('emails.gift_voucher.printable') }}</a>
|
||||
<a href="{{ $printUrl }}" style="color:#1d4ed8; text-decoration:none;">{{ __('emails.gift_voucher.printable') }}</a>
|
||||
</p>
|
||||
@endisset
|
||||
</div>
|
||||
|
||||
<p style="font-size: 14px; color: #4b5563; margin: 12px 0;">
|
||||
{{ __('emails.gift_voucher.expiry', ['date' => optional($voucher->expires_at)->toFormattedDateString()]) }}
|
||||
</p>
|
||||
|
||||
<p style="font-size: 14px; color: #4b5563; margin: 12px 0;">
|
||||
{!! __('emails.gift_voucher.withdrawal', ['url' => $withdrawalUrl]) !!}
|
||||
</p>
|
||||
@endsection
|
||||
|
||||
<p style="font-size: 14px; color: #4b5563; margin-top: 20px;">
|
||||
{!! __('emails.gift_voucher.footer') !!}
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@section('footer')
|
||||
{!! __('emails.gift_voucher.footer') !!}
|
||||
@endsection
|
||||
|
||||
40
resources/views/emails/notifications/basic.blade.php
Normal file
40
resources/views/emails/notifications/basic.blade.php
Normal file
@@ -0,0 +1,40 @@
|
||||
@extends('emails.partials.layout')
|
||||
|
||||
@section('title', $title)
|
||||
@section('preheader', $preheader ?? '')
|
||||
@section('hero_title', $heroTitle)
|
||||
|
||||
@isset($heroSubtitle)
|
||||
@section('hero_subtitle', $heroSubtitle)
|
||||
@endisset
|
||||
|
||||
@section('content')
|
||||
@isset($intro)
|
||||
<p style="margin:0 0 12px; font-size:15px; color:#1f2937;">
|
||||
{{ $intro }}
|
||||
</p>
|
||||
@endisset
|
||||
@if (! empty($lines))
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="border-collapse:collapse;">
|
||||
@foreach ($lines as $line)
|
||||
<tr>
|
||||
<td style="padding:6px 0; font-size:14px; color:#0f172a;">{{ $line }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
@endif
|
||||
@endsection
|
||||
|
||||
@section('cta')
|
||||
@if (! empty($cta))
|
||||
@foreach ($cta as $action)
|
||||
<a href="{{ $action['url'] }}" style="display:inline-block; background-color:#111827; color:#ffffff; text-decoration:none; padding:12px 20px; border-radius:999px; font-weight:600; font-size:14px; margin-right:12px; margin-bottom:12px;">
|
||||
{{ $action['label'] }}
|
||||
</a>
|
||||
@endforeach
|
||||
@endif
|
||||
@endsection
|
||||
|
||||
@section('footer')
|
||||
{!! $footer ?? __('emails.brand.footer') !!}
|
||||
@endsection
|
||||
55
resources/views/emails/partials/layout.blade.php
Normal file
55
resources/views/emails/partials/layout.blade.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>@yield('title')</title>
|
||||
</head>
|
||||
<body style="margin:0; padding:0; background-color:#f4f5f7; font-family:Arial, Helvetica, sans-serif; color:#1a1a1a;">
|
||||
<span style="display:none; font-size:0; line-height:0; max-height:0; max-width:0; opacity:0; overflow:hidden;">
|
||||
@yield('preheader', '')
|
||||
</span>
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color:#f4f5f7; padding:32px 0;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table role="presentation" width="640" cellspacing="0" cellpadding="0" style="background-color:#ffffff; border-radius:16px; overflow:hidden; box-shadow:0 12px 30px rgba(15, 23, 42, 0.08);">
|
||||
<tr>
|
||||
<td style="background:linear-gradient(135deg,#0f172a,#334155); color:#ffffff; padding:32px;">
|
||||
<p style="margin:0 0 10px; font-size:12px; letter-spacing:0.12em; text-transform:uppercase; opacity:0.7;">
|
||||
@yield('brand_label', __('emails.brand.label'))
|
||||
</p>
|
||||
<h1 style="margin:0; font-size:24px; line-height:1.35;">
|
||||
@yield('hero_title')
|
||||
</h1>
|
||||
@hasSection('hero_subtitle')
|
||||
<p style="margin:12px 0 0; font-size:15px; opacity:0.9;">
|
||||
@yield('hero_subtitle')
|
||||
</p>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:28px 32px 12px;">
|
||||
@yield('content')
|
||||
</td>
|
||||
</tr>
|
||||
@hasSection('cta')
|
||||
<tr>
|
||||
<td style="padding:0 32px 24px;">
|
||||
@yield('cta')
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
<tr>
|
||||
<td style="padding:0 32px 32px; font-size:12px; color:#6b7280;">
|
||||
@yield('footer', __('emails.brand.footer'))
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="margin:16px 0 0; font-size:12px; color:#94a3b8;">
|
||||
@yield('brand_footer', __('emails.brand.tagline'))
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,116 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ __('emails.purchase.subject', ['package' => $packageName]) }}</title>
|
||||
</head>
|
||||
<body style="margin:0; padding:0; background-color:#f4f5f7; font-family:Arial, Helvetica, sans-serif; color:#1a1a1a;">
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color:#f4f5f7; padding:32px 0;">
|
||||
@extends('emails.partials.layout')
|
||||
|
||||
@section('title', __('emails.purchase.subject', ['package' => $packageName]))
|
||||
@section('preheader', __('emails.purchase.subtitle'))
|
||||
@section('hero_title', __('emails.purchase.greeting', ['name' => $user->fullName]))
|
||||
@section('hero_subtitle', __('emails.purchase.subtitle'))
|
||||
|
||||
@section('content')
|
||||
<h2 style="margin:0 0 12px; font-size:18px;">
|
||||
{{ __('emails.purchase.summary_title') }}
|
||||
</h2>
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="border-collapse:collapse;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table role="presentation" width="600" cellspacing="0" cellpadding="0" style="background-color:#ffffff; border-radius:16px; overflow:hidden; box-shadow:0 12px 30px rgba(15, 23, 42, 0.08);">
|
||||
<tr>
|
||||
<td style="background:linear-gradient(135deg,#0f172a,#334155); color:#ffffff; padding:32px;">
|
||||
<p style="margin:0 0 10px; font-size:12px; letter-spacing:0.12em; text-transform:uppercase; opacity:0.7;">
|
||||
{{ __('emails.purchase.brand_label') }}
|
||||
</p>
|
||||
<h1 style="margin:0; font-size:24px; line-height:1.35;">
|
||||
{{ __('emails.purchase.greeting', ['name' => $user->fullName]) }}
|
||||
</h1>
|
||||
<p style="margin:12px 0 0; font-size:15px; opacity:0.9;">
|
||||
{{ __('emails.purchase.subtitle') }}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:28px 32px 12px;">
|
||||
<h2 style="margin:0 0 12px; font-size:18px;">
|
||||
{{ __('emails.purchase.summary_title') }}
|
||||
</h2>
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="border-collapse:collapse;">
|
||||
<tr>
|
||||
<td style="padding:10px 0; font-size:14px; color:#6b7280;">{{ __('emails.purchase.package_label') }}</td>
|
||||
<td style="padding:10px 0; font-size:14px; text-align:right; font-weight:600;">{{ $packageName }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px 0; font-size:14px; color:#6b7280;">{{ __('emails.purchase.type_label') }}</td>
|
||||
<td style="padding:10px 0; font-size:14px; text-align:right;">{{ $packageTypeLabel }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px 0; font-size:14px; color:#6b7280;">{{ __('emails.purchase.date_label') }}</td>
|
||||
<td style="padding:10px 0; font-size:14px; text-align:right;">{{ $purchaseDate }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px 0; font-size:14px; color:#6b7280;">{{ __('emails.purchase.provider_label') }}</td>
|
||||
<td style="padding:10px 0; font-size:14px; text-align:right;">{{ $providerLabel }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px 0; font-size:14px; color:#6b7280;">{{ __('emails.purchase.order_label') }}</td>
|
||||
<td style="padding:10px 0; font-size:14px; text-align:right;">{{ $orderId }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px 0; font-size:14px; color:#6b7280;">{{ __('emails.purchase.price_label') }}</td>
|
||||
<td style="padding:10px 0; font-size:16px; text-align:right; font-weight:700;">{{ $priceFormatted }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:0 32px 18px;">
|
||||
<div style="background-color:#f8fafc; border:1px solid #e2e8f0; border-radius:12px; padding:16px;">
|
||||
<p style="margin:0 0 6px; font-size:13px; text-transform:uppercase; letter-spacing:0.08em; color:#64748b;">
|
||||
{{ __('emails.purchase.activation_label') }}
|
||||
</p>
|
||||
<p style="margin:0; font-size:14px; color:#0f172a;">
|
||||
{{ __('emails.purchase.activation') }}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@if (! empty($limits))
|
||||
<tr>
|
||||
<td style="padding:0 32px 18px;">
|
||||
<h3 style="margin:0 0 12px; font-size:16px;">{{ __('emails.purchase.limits_title') }}</h3>
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="border-collapse:collapse;">
|
||||
@foreach ($limits as $limit)
|
||||
<tr>
|
||||
<td style="padding:6px 0; font-size:14px; color:#6b7280;">{{ $limit['label'] }}</td>
|
||||
<td style="padding:6px 0; font-size:14px; text-align:right; font-weight:600;">{{ $limit['value'] }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
@if ($invoiceUrl)
|
||||
<tr>
|
||||
<td style="padding:0 32px 18px;">
|
||||
<h3 style="margin:0 0 8px; font-size:16px;">{{ __('emails.purchase.invoice_title') }}</h3>
|
||||
<p style="margin:0; font-size:14px; color:#6b7280;">
|
||||
<a href="{{ $invoiceUrl }}" style="color:#1d4ed8; text-decoration:none;">
|
||||
{{ __('emails.purchase.invoice_link') }}
|
||||
</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
<tr>
|
||||
<td style="padding:0 32px 32px;">
|
||||
<a href="{{ $ctaUrl }}" style="display:inline-block; background-color:#111827; color:#ffffff; text-decoration:none; padding:12px 20px; border-radius:999px; font-weight:600; font-size:14px;">
|
||||
{{ __('emails.purchase.cta') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:0 32px 32px; font-size:12px; color:#6b7280;">
|
||||
{!! __('emails.purchase.footer') !!}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="margin:16px 0 0; font-size:12px; color:#94a3b8;">
|
||||
{{ __('emails.purchase.brand_footer') }}
|
||||
</p>
|
||||
</td>
|
||||
<td style="padding:10px 0; font-size:14px; color:#6b7280;">{{ __('emails.purchase.package_label') }}</td>
|
||||
<td style="padding:10px 0; font-size:14px; text-align:right; font-weight:600;">{{ $packageName }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px 0; font-size:14px; color:#6b7280;">{{ __('emails.purchase.type_label') }}</td>
|
||||
<td style="padding:10px 0; font-size:14px; text-align:right;">{{ $packageTypeLabel }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px 0; font-size:14px; color:#6b7280;">{{ __('emails.purchase.date_label') }}</td>
|
||||
<td style="padding:10px 0; font-size:14px; text-align:right;">{{ $purchaseDate }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px 0; font-size:14px; color:#6b7280;">{{ __('emails.purchase.provider_label') }}</td>
|
||||
<td style="padding:10px 0; font-size:14px; text-align:right;">{{ $providerLabel }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px 0; font-size:14px; color:#6b7280;">{{ __('emails.purchase.order_label') }}</td>
|
||||
<td style="padding:10px 0; font-size:14px; text-align:right;">{{ $orderId }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px 0; font-size:14px; color:#6b7280;">{{ __('emails.purchase.price_label') }}</td>
|
||||
<td style="padding:10px 0; font-size:16px; text-align:right; font-weight:700;">{{ $priceFormatted }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
<div style="margin-top:16px; background-color:#f8fafc; border:1px solid #e2e8f0; border-radius:12px; padding:16px;">
|
||||
<p style="margin:0 0 6px; font-size:13px; text-transform:uppercase; letter-spacing:0.08em; color:#64748b;">
|
||||
{{ __('emails.purchase.activation_label') }}
|
||||
</p>
|
||||
<p style="margin:0; font-size:14px; color:#0f172a;">
|
||||
{{ __('emails.purchase.activation') }}
|
||||
</p>
|
||||
</div>
|
||||
@if (! empty($limits))
|
||||
<h3 style="margin:18px 0 12px; font-size:16px;">{{ __('emails.purchase.limits_title') }}</h3>
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="border-collapse:collapse;">
|
||||
@foreach ($limits as $limit)
|
||||
<tr>
|
||||
<td style="padding:6px 0; font-size:14px; color:#6b7280;">{{ $limit['label'] }}</td>
|
||||
<td style="padding:6px 0; font-size:14px; text-align:right; font-weight:600;">{{ $limit['value'] }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
@endif
|
||||
@if ($invoiceUrl)
|
||||
<h3 style="margin:18px 0 8px; font-size:16px;">{{ __('emails.purchase.invoice_title') }}</h3>
|
||||
<p style="margin:0; font-size:14px; color:#6b7280;">
|
||||
<a href="{{ $invoiceUrl }}" style="color:#1d4ed8; text-decoration:none;">
|
||||
{{ __('emails.purchase.invoice_link') }}
|
||||
</a>
|
||||
</p>
|
||||
@endif
|
||||
@endsection
|
||||
|
||||
@section('cta')
|
||||
<a href="{{ $ctaUrl }}" style="display:inline-block; background-color:#111827; color:#ffffff; text-decoration:none; padding:12px 20px; border-radius:999px; font-weight:600; font-size:14px;">
|
||||
{{ __('emails.purchase.cta') }}
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
@section('footer')
|
||||
{!! __('emails.purchase.footer') !!}
|
||||
@endsection
|
||||
|
||||
29
resources/views/emails/verify-email.blade.php
Normal file
29
resources/views/emails/verify-email.blade.php
Normal file
@@ -0,0 +1,29 @@
|
||||
@extends('emails.partials.layout')
|
||||
|
||||
@section('title', __('emails.verification.subject'))
|
||||
@section('preheader', __('emails.verification.preheader'))
|
||||
@section('hero_title', __('emails.verification.hero_title', ['name' => $user->fullName ?? $user->name ?? $user->email]))
|
||||
@section('hero_subtitle', __('emails.verification.hero_subtitle'))
|
||||
|
||||
@section('content')
|
||||
<p style="margin:0 0 12px; font-size:15px; color:#1f2937;">
|
||||
{{ __('emails.verification.body') }}
|
||||
</p>
|
||||
<p style="margin:0 0 16px; font-size:14px; color:#1f2937;">
|
||||
{{ __('emails.verification.expires', ['minutes' => $expiresIn]) }}
|
||||
</p>
|
||||
<p style="margin:0; font-size:13px; color:#6b7280;">
|
||||
{{ __('emails.verification.link_fallback') }}<br>
|
||||
<span style="word-break:break-all;">{{ $verificationUrl }}</span>
|
||||
</p>
|
||||
@endsection
|
||||
|
||||
@section('cta')
|
||||
<a href="{{ $verificationUrl }}" style="display:inline-block; background-color:#111827; color:#ffffff; text-decoration:none; padding:12px 20px; border-radius:999px; font-weight:600; font-size:14px;">
|
||||
{{ __('emails.verification.cta') }}
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
@section('footer')
|
||||
{!! __('emails.verification.footer') !!}
|
||||
@endsection
|
||||
@@ -1,14 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ __('emails.welcome.subject', ['name' => $user->fullName]) }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{ __('emails.welcome.greeting', ['name' => $user->fullName]) }}</h1>
|
||||
<p>{{ __('emails.welcome.body') }}</p>
|
||||
<p>{{ __('emails.welcome.username', ['username' => $user->username]) }}</p>
|
||||
<p>{{ __('emails.welcome.email', ['email' => $user->email]) }}</p>
|
||||
<p>{{ __('emails.welcome.verification') }}</p>
|
||||
<p>{!! __('emails.welcome.footer') !!}</p>
|
||||
</body>
|
||||
</html>
|
||||
@extends('emails.partials.layout')
|
||||
|
||||
@section('title', __('emails.welcome.subject', ['name' => $user->fullName]))
|
||||
@section('preheader', __('emails.welcome.subtitle'))
|
||||
@section('hero_title', __('emails.welcome.greeting', ['name' => $user->fullName]))
|
||||
@section('hero_subtitle', __('emails.welcome.subtitle'))
|
||||
|
||||
@section('content')
|
||||
<p style="margin:0 0 12px; font-size:15px; color:#1f2937;">
|
||||
{{ __('emails.welcome.body') }}
|
||||
</p>
|
||||
<div style="background-color:#f8fafc; border:1px solid #e2e8f0; border-radius:12px; padding:16px; margin-bottom:16px;">
|
||||
<p style="margin:0 0 6px; font-size:13px; text-transform:uppercase; letter-spacing:0.08em; color:#64748b;">
|
||||
{{ __('emails.welcome.account_label') }}
|
||||
</p>
|
||||
<p style="margin:0; font-size:14px; color:#0f172a;">
|
||||
{{ __('emails.welcome.username', ['username' => $user->username]) }}<br>
|
||||
{{ __('emails.welcome.email', ['email' => $user->email]) }}
|
||||
</p>
|
||||
</div>
|
||||
<p style="margin:0 0 16px; font-size:14px; color:#1f2937;">
|
||||
{{ __('emails.welcome.verification') }}
|
||||
</p>
|
||||
<h3 style="margin:0 0 10px; font-size:16px;">{{ __('emails.welcome.next_steps_title') }}</h3>
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="border-collapse:collapse;">
|
||||
<tr>
|
||||
<td style="padding:6px 0; font-size:14px; color:#6b7280;">01</td>
|
||||
<td style="padding:6px 0; font-size:14px; color:#0f172a;">{{ __('emails.welcome.step_one') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:6px 0; font-size:14px; color:#6b7280;">02</td>
|
||||
<td style="padding:6px 0; font-size:14px; color:#0f172a;">{{ __('emails.welcome.step_two') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:6px 0; font-size:14px; color:#6b7280;">03</td>
|
||||
<td style="padding:6px 0; font-size:14px; color:#0f172a;">{{ __('emails.welcome.step_three') }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
@endsection
|
||||
|
||||
@section('cta')
|
||||
<a href="{{ url('/event-admin') }}" style="display:inline-block; background-color:#111827; color:#ffffff; text-decoration:none; padding:12px 20px; border-radius:999px; font-weight:600; font-size:14px;">
|
||||
{{ __('emails.welcome.cta') }}
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
@section('footer')
|
||||
{!! __('emails.welcome.footer') !!}
|
||||
@endsection
|
||||
|
||||
71
tests/Feature/BrandedNotificationEmailsTest.php
Normal file
71
tests/Feature/BrandedNotificationEmailsTest.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\PackagePurchase;
|
||||
use App\Models\Tenant;
|
||||
use App\Notifications\Customer\RefundReceipt;
|
||||
use App\Notifications\InactiveTenantDeletionWarning;
|
||||
use App\Notifications\Ops\PurchaseCreated;
|
||||
use App\Notifications\UploadPipelineFailed;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Tests\TestCase;
|
||||
|
||||
class BrandedNotificationEmailsTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_upload_pipeline_failed_uses_branded_view(): void
|
||||
{
|
||||
$notification = new UploadPipelineFailed([
|
||||
'job' => 'UploadJob',
|
||||
'queue' => 'uploads',
|
||||
'event_id' => 123,
|
||||
'photo_id' => 456,
|
||||
'exception' => 'ExampleException',
|
||||
]);
|
||||
|
||||
$mailMessage = $notification->toMail((object) []);
|
||||
|
||||
$this->assertInstanceOf(MailMessage::class, $mailMessage);
|
||||
$this->assertSame('emails.notifications.basic', $mailMessage->view);
|
||||
$this->assertArrayHasKey('lines', $mailMessage->viewData);
|
||||
}
|
||||
|
||||
public function test_inactive_tenant_deletion_warning_uses_branded_view(): void
|
||||
{
|
||||
$tenant = Tenant::factory()->create([
|
||||
'name' => 'Demo Tenant',
|
||||
]);
|
||||
|
||||
$notification = new InactiveTenantDeletionWarning($tenant, Carbon::now()->addDays(10));
|
||||
$mailMessage = $notification->toMail((object) []);
|
||||
|
||||
$this->assertInstanceOf(MailMessage::class, $mailMessage);
|
||||
$this->assertSame('emails.notifications.basic', $mailMessage->view);
|
||||
$this->assertArrayHasKey('cta', $mailMessage->viewData);
|
||||
}
|
||||
|
||||
public function test_refund_receipt_uses_branded_view(): void
|
||||
{
|
||||
$purchase = PackagePurchase::factory()->create();
|
||||
$notification = new RefundReceipt($purchase);
|
||||
$mailMessage = $notification->toMail((object) []);
|
||||
|
||||
$this->assertInstanceOf(MailMessage::class, $mailMessage);
|
||||
$this->assertSame('emails.notifications.basic', $mailMessage->view);
|
||||
$this->assertArrayHasKey('footer', $mailMessage->viewData);
|
||||
}
|
||||
|
||||
public function test_ops_purchase_created_uses_branded_view(): void
|
||||
{
|
||||
$purchase = PackagePurchase::factory()->create();
|
||||
$notification = new PurchaseCreated($purchase);
|
||||
$mailMessage = $notification->toMail((object) []);
|
||||
|
||||
$this->assertInstanceOf(MailMessage::class, $mailMessage);
|
||||
$this->assertSame('emails.notifications.basic', $mailMessage->view);
|
||||
}
|
||||
}
|
||||
119
tests/Feature/CustomerEmailRenderTest.php
Normal file
119
tests/Feature/CustomerEmailRenderTest.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Models\EventPackage;
|
||||
use App\Models\EventPackageAddon;
|
||||
use App\Models\GiftVoucher;
|
||||
use App\Models\Package;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class CustomerEmailRenderTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_customer_emails_render_in_de(): void
|
||||
{
|
||||
app()->setLocale('de');
|
||||
|
||||
[$user, $tenant, $package] = $this->makeCustomerContext();
|
||||
|
||||
$welcome = view('emails.welcome', ['user' => $user])->render();
|
||||
$this->assertStringContainsString('Die Fotospiel.App', $welcome);
|
||||
|
||||
$abandoned = view('emails.abandoned-checkout', [
|
||||
'user' => $user,
|
||||
'package' => $package,
|
||||
'packageName' => $package->name,
|
||||
'timing' => '1h',
|
||||
'resumeUrl' => 'https://example.test/checkout',
|
||||
])->render();
|
||||
$this->assertStringContainsString($package->name, $abandoned);
|
||||
|
||||
$contact = view('emails.contact-confirmation', ['name' => 'Soren'])->render();
|
||||
$this->assertStringContainsString('Soren', $contact);
|
||||
|
||||
$event = Event::factory()->create(['tenant_id' => $tenant->id]);
|
||||
$eventPackage = EventPackage::create([
|
||||
'event_id' => $event->id,
|
||||
'package_id' => $package->id,
|
||||
'purchased_price' => 59,
|
||||
'purchased_at' => now(),
|
||||
]);
|
||||
$addon = EventPackageAddon::create([
|
||||
'event_package_id' => $eventPackage->id,
|
||||
'event_id' => $event->id,
|
||||
'tenant_id' => $tenant->id,
|
||||
'addon_key' => 'extra_photos',
|
||||
'extra_photos' => 250,
|
||||
'amount' => 9.0,
|
||||
'currency' => 'EUR',
|
||||
'status' => 'completed',
|
||||
'metadata' => ['label' => 'Extra Fotos'],
|
||||
'purchased_at' => now(),
|
||||
]);
|
||||
$receipt = view('emails.addons.receipt', ['addon' => $addon])->render();
|
||||
$this->assertStringContainsString('Extra Fotos', $receipt);
|
||||
|
||||
$voucher = GiftVoucher::factory()->create([
|
||||
'message' => 'Herzlichen Glückwunsch!',
|
||||
]);
|
||||
$gift = view('emails.gift-voucher', [
|
||||
'voucher' => $voucher,
|
||||
'amount' => number_format((float) $voucher->amount, 2),
|
||||
'currency' => $voucher->currency,
|
||||
'forRecipient' => false,
|
||||
'printUrl' => 'https://example.test/print',
|
||||
])->render();
|
||||
$this->assertStringContainsString($voucher->code, $gift);
|
||||
}
|
||||
|
||||
public function test_customer_emails_render_in_en(): void
|
||||
{
|
||||
app()->setLocale('en');
|
||||
|
||||
[$user, $tenant, $package] = $this->makeCustomerContext();
|
||||
|
||||
$welcome = view('emails.welcome', ['user' => $user])->render();
|
||||
$this->assertStringContainsString('Die Fotospiel.App', $welcome);
|
||||
|
||||
$abandoned = view('emails.abandoned-checkout', [
|
||||
'user' => $user,
|
||||
'package' => $package,
|
||||
'packageName' => $package->name,
|
||||
'timing' => '24h',
|
||||
'resumeUrl' => 'https://example.test/checkout',
|
||||
])->render();
|
||||
$this->assertStringContainsString('Resume checkout', $abandoned);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: User, 1: Tenant, 2: Package}
|
||||
*/
|
||||
private function makeCustomerContext(): array
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'first_name' => 'Soren',
|
||||
'last_name' => 'Eberhardt',
|
||||
'username' => 'soren',
|
||||
]);
|
||||
$tenant = Tenant::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
]);
|
||||
$user->forceFill(['tenant_id' => $tenant->id])->save();
|
||||
|
||||
$package = Package::factory()->create([
|
||||
'name' => 'Standard',
|
||||
'type' => 'endcustomer',
|
||||
'max_photos' => 500,
|
||||
'max_guests' => 200,
|
||||
'gallery_days' => 30,
|
||||
]);
|
||||
|
||||
return [$user, $tenant, $package];
|
||||
}
|
||||
}
|
||||
44
tests/Feature/VerifyEmailNotificationTest.php
Normal file
44
tests/Feature/VerifyEmailNotificationTest.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Notifications\VerifyEmailNotification;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Tests\TestCase;
|
||||
|
||||
class VerifyEmailNotificationTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_user_sends_custom_verify_email_notification(): void
|
||||
{
|
||||
Notification::fake();
|
||||
|
||||
$user = User::factory()->create([
|
||||
'email_verified_at' => null,
|
||||
]);
|
||||
|
||||
$user->sendEmailVerificationNotification();
|
||||
|
||||
Notification::assertSentTo($user, VerifyEmailNotification::class);
|
||||
}
|
||||
|
||||
public function test_verify_email_notification_uses_custom_view(): void
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'email_verified_at' => null,
|
||||
]);
|
||||
|
||||
$notification = new VerifyEmailNotification;
|
||||
$mailMessage = $notification->toMail($user);
|
||||
|
||||
$this->assertInstanceOf(MailMessage::class, $mailMessage);
|
||||
$this->assertSame('emails.verify-email', $mailMessage->view);
|
||||
$this->assertArrayHasKey('verificationUrl', $mailMessage->viewData);
|
||||
$this->assertArrayHasKey('user', $mailMessage->viewData);
|
||||
$this->assertSame($user->getKey(), $mailMessage->viewData['user']->getKey());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user