diff --git a/app/Http/Controllers/Qr/AccountController.php b/app/Http/Controllers/Qr/AccountController.php
index 333d75c..04bb101 100644
--- a/app/Http/Controllers/Qr/AccountController.php
+++ b/app/Http/Controllers/Qr/AccountController.php
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Qr;
use App\Http\Controllers\Controller;
use App\Models\QrSetting;
use App\Services\Billing\BillingClient;
+use App\Support\AccountBranding;
use App\Support\Qr\QrCornerStyleCatalog;
use App\Support\Qr\QrFrameStyleCatalog;
use App\Support\Qr\QrModuleStyleCatalog;
@@ -112,11 +113,25 @@ class AccountController extends Controller
'default_style.gradient_color1' => ['nullable', 'regex:/^#[0-9a-fA-F]{6}$/'],
'default_style.gradient_color2' => ['nullable', 'regex:/^#[0-9a-fA-F]{6}$/'],
'default_style.gradient_rotation' => ['nullable', 'integer', 'min:0', 'max:360'],
+ 'logo' => ['nullable', 'image', 'mimes:jpeg,png,jpg,webp,svg', 'max:2048'],
+ 'remove_logo' => ['nullable', 'boolean'],
]);
$eventDefaults = $data['event_defaults'] ?? [];
$eventDefaults['registration_open'] = $request->boolean('event_defaults.registration_open');
+ $settings = $account->getOrCreateQrSetting();
+ $logoPath = $settings->logo_path;
+
+ if ($request->boolean('remove_logo')) {
+ AccountBranding::deleteStoredLogo($settings);
+ $logoPath = null;
+ }
+
+ if ($request->hasFile('logo')) {
+ $logoPath = AccountBranding::storeLogo($settings, $request->file('logo'));
+ }
+
QrSetting::updateOrCreate(
['user_id' => $account->id],
[
@@ -127,6 +142,7 @@ class AccountController extends Controller
'notify_payouts' => $request->boolean('notify_payouts'),
'event_defaults' => QrSetting::sanitizeEventDefaults($eventDefaults),
'default_style' => QrSetting::sanitizeDefaultStyle($data['default_style'] ?? null),
+ 'logo_path' => $logoPath,
],
);
diff --git a/app/Models/QrSetting.php b/app/Models/QrSetting.php
index 7bbbcc1..631e8e7 100644
--- a/app/Models/QrSetting.php
+++ b/app/Models/QrSetting.php
@@ -20,6 +20,7 @@ class QrSetting extends Model
'default_type',
'default_style',
'event_defaults',
+ 'logo_path',
];
protected $casts = [
diff --git a/app/Services/Events/EventEmailService.php b/app/Services/Events/EventEmailService.php
index 918da60..02826f2 100644
--- a/app/Services/Events/EventEmailService.php
+++ b/app/Services/Events/EventEmailService.php
@@ -2,6 +2,7 @@
namespace App\Services\Events;
+use App\Support\AccountBranding;
use Illuminate\Support\Facades\View;
class EventEmailService
@@ -129,6 +130,10 @@ class EventEmailService
?string $replyToEmail = null,
?string $replyToName = null,
): bool {
+ $brandingSettings = AccountBranding::forOwnerRef($ownerPublicId);
+ $viewData['logoUrl'] = AccountBranding::emailLogoUrl($brandingSettings);
+ $viewData['companyName'] = AccountBranding::companyName($brandingSettings);
+
$html = View::make($view, $viewData)->render();
$text = strip_tags(str_replace(['
', '
', '
'], "\n", $html));
diff --git a/app/Support/AccountBranding.php b/app/Support/AccountBranding.php
new file mode 100644
index 0000000..31643e3
--- /dev/null
+++ b/app/Support/AccountBranding.php
@@ -0,0 +1,90 @@
+getOrCreateQrSetting();
+ }
+
+ public static function forOwnerRef(string $ownerPublicId): ?QrSetting
+ {
+ $user = User::query()->where('public_id', $ownerPublicId)->first();
+
+ return $user ? $user->getOrCreateQrSetting() : null;
+ }
+
+ public static function companyName(?QrSetting $settings, ?User $user = null): string
+ {
+ if ($user) {
+ return (string) ($user->name ?: 'Company');
+ }
+
+ if ($settings?->user) {
+ return (string) ($settings->user->name ?: 'Company');
+ }
+
+ if ($settings?->user_id) {
+ $owner = User::query()->find($settings->user_id);
+
+ return (string) ($owner?->name ?: 'Company');
+ }
+
+ return 'Company';
+ }
+
+ public static function hasCustomLogo(?QrSetting $settings): bool
+ {
+ return $settings !== null
+ && $settings->logo_path !== null
+ && Storage::disk('public')->exists($settings->logo_path);
+ }
+
+ public static function emailLogoUrl(?QrSetting $settings): ?string
+ {
+ if (! self::hasCustomLogo($settings)) {
+ return null;
+ }
+
+ $ext = strtolower(pathinfo((string) $settings->logo_path, PATHINFO_EXTENSION));
+ if ($ext === 'svg') {
+ return null;
+ }
+
+ $version = $settings->updated_at?->getTimestamp() ?? time();
+
+ return Storage::disk('public')->url($settings->logo_path).'?v='.$version;
+ }
+
+ public static function logoUrl(?QrSetting $settings): ?string
+ {
+ if (! self::hasCustomLogo($settings)) {
+ return null;
+ }
+
+ $version = $settings->updated_at?->getTimestamp() ?? time();
+
+ return Storage::disk('public')->url($settings->logo_path).'?v='.$version;
+ }
+
+ public static function storeLogo(QrSetting $settings, UploadedFile $file): string
+ {
+ self::deleteStoredLogo($settings);
+
+ return $file->store('events/accounts/'.$settings->user_id, 'public');
+ }
+
+ public static function deleteStoredLogo(QrSetting $settings): void
+ {
+ if ($settings->logo_path) {
+ Storage::disk('public')->delete($settings->logo_path);
+ }
+ }
+}
diff --git a/database/migrations/2026_07_12_200000_add_logo_path_to_qr_settings_table.php b/database/migrations/2026_07_12_200000_add_logo_path_to_qr_settings_table.php
new file mode 100644
index 0000000..1edb9c6
--- /dev/null
+++ b/database/migrations/2026_07_12_200000_add_logo_path_to_qr_settings_table.php
@@ -0,0 +1,22 @@
+string('logo_path')->nullable()->after('event_defaults');
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('qr_settings', function (Blueprint $table) {
+ $table->dropColumn('logo_path');
+ });
+ }
+};
diff --git a/resources/views/email/customer-branded.blade.php b/resources/views/email/customer-branded.blade.php
new file mode 100644
index 0000000..81411c5
--- /dev/null
+++ b/resources/views/email/customer-branded.blade.php
@@ -0,0 +1,22 @@
+
+
+
{{ $companyName }}
+