Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/Filament/Admin/Resources/PolydockStoreAppResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ public static function form(Form $form): Form
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
Forms\Components\Select::make('mail_theme')
->label('Email Theme')
->options(fn() => array_merge(['null' => 'Default'], array_combine(app('mail.themes'), app('mail.themes'))))
->nullable()
->dehydrateStateUsing(fn(?string $state) => $state === 'null' ? null : $state),
Forms\Components\Textarea::make('description')
->required()
->columnSpanFull(),
Expand Down
20 changes: 10 additions & 10 deletions app/Mail/AppInstanceMidtrialMail.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Mail;

use App\Mail\Traits\ResolvesThemeTemplate;
use App\Models\PolydockAppInstance;
use App\Models\User;
use Illuminate\Bus\Queueable;
Expand All @@ -10,23 +11,22 @@

class AppInstanceMidtrialMail extends Mailable
{
use Queueable, SerializesModels;
use Queueable, SerializesModels, ResolvesThemeTemplate;

public PolydockAppInstance $appInstance;
public User $toUser;

public function __construct(PolydockAppInstance $appInstance, User $toUser)
{
$this->appInstance = $appInstance;
$this->toUser = $toUser;
}
public function __construct(
public PolydockAppInstance $appInstance,
public User $toUser,
public string $markdownTemplate = 'emails.app-instance.midtrial'
) {}

public function build()
{
$this->resolveThemeTemplate($this->appInstance->storeApp->mail_theme, $this->markdownTemplate);

$subject = $this->appInstance->storeApp->midtrial_email_subject ?? 'Halfway Through Your Trial';
$subject .= " [" . $this->appInstance->name . "]";

return $this->markdown('emails.app-instance.midtrial')
return $this->markdown($this->markdownTemplate)
->subject($subject);
}
}
19 changes: 10 additions & 9 deletions app/Mail/AppInstanceOneDayLeftMail.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Mail;

use App\Mail\Traits\ResolvesThemeTemplate;
use App\Models\PolydockAppInstance;
use App\Models\User;
use Illuminate\Bus\Queueable;
Expand All @@ -10,23 +11,23 @@

class AppInstanceOneDayLeftMail extends Mailable
{
use Queueable, SerializesModels;
use Queueable, SerializesModels, ResolvesThemeTemplate;

public PolydockAppInstance $appInstance;
public User $toUser;

public function __construct(PolydockAppInstance $appInstance, User $toUser)
{
$this->appInstance = $appInstance;
$this->toUser = $toUser;
public function __construct(
public PolydockAppInstance $appInstance,
public User $toUser,
public string $markdownTemplate = 'emails.app-instance.one-day-left'
) {
}

public function build()
{
$this->resolveThemeTemplate($this->appInstance->storeApp->mail_theme, $this->markdownTemplate);

$subject = $this->appInstance->storeApp->one_day_left_email_subject ?? 'One Day Left in Your Trial';
$subject .= " [" . $this->appInstance->name . "]";

return $this->markdown('emails.app-instance.one-day-left')
return $this->markdown($this->markdownTemplate)
->subject($subject);
}
}
13 changes: 9 additions & 4 deletions app/Mail/AppInstanceReadyMail.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Mail;

use App\Mail\Traits\ResolvesThemeTemplate;
use App\Models\PolydockAppInstance;
use App\Models\User;
use Illuminate\Bus\Queueable;
Expand All @@ -12,21 +13,25 @@

class AppInstanceReadyMail extends Mailable
{
use Queueable, SerializesModels;
use Queueable, SerializesModels, ResolvesThemeTemplate;

/**
* Create a new message instance.
*/
public function __construct(
public PolydockAppInstance $appInstance,
public User $toUser
) {}
public User $toUser,
public string $markdownTemplate = 'emails.app-instance.ready'
) {
}

/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
$this->resolveThemeTemplate($this->appInstance->storeApp->mail_theme, $this->markdownTemplate);

$subject = $this->appInstance->storeApp->email_subject_line;

if (empty($subject)) {
Expand All @@ -46,7 +51,7 @@ public function envelope(): Envelope
public function content(): Content
{
return new Content(
markdown: 'emails.app-instance.ready',
markdown: $this->markdownTemplate,
);
}

Expand Down
20 changes: 10 additions & 10 deletions app/Mail/AppInstanceTrialCompleteMail.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Mail;

use App\Mail\Traits\ResolvesThemeTemplate;
use App\Models\PolydockAppInstance;
use App\Models\User;
use Illuminate\Bus\Queueable;
Expand All @@ -10,23 +11,22 @@

class AppInstanceTrialCompleteMail extends Mailable
{
use Queueable, SerializesModels;
use Queueable, SerializesModels, ResolvesThemeTemplate;

public PolydockAppInstance $appInstance;
public User $toUser;

public function __construct(PolydockAppInstance $appInstance, User $toUser)
{
$this->appInstance = $appInstance;
$this->toUser = $toUser;
}
public function __construct(
public PolydockAppInstance $appInstance,
public User $toUser,
public string $markdownTemplate = 'emails.app-instance.trial-complete'
) {}

public function build()
{
$this->resolveThemeTemplate($this->appInstance->storeApp->mail_theme, $this->markdownTemplate);

$subject = $this->appInstance->storeApp->trial_complete_email_subject ?? 'Your Trial Has Ended';
$subject .= " [" . $this->appInstance->name . "]";

return $this->markdown('emails.app-instance.trial-complete')
return $this->markdown($this->markdownTemplate)
->subject($subject);
}
}
35 changes: 35 additions & 0 deletions app/Mail/Traits/ResolvesThemeTemplate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace App\Mail\Traits;

use Illuminate\Support\Facades\View;

trait ResolvesThemeTemplate
{
/**
* Resolve the theme and markdown template paths.
* Sets $this->theme and $this->markdownTemplate accordingly.
*
* @param string $themeBase The theme namespace (e.g., 'promet')
* @param string $markdownTemplate The default markdown template path
* @throws \Exception
*/
protected function resolveThemeTemplate(string|null $themeBase, string $markdownTemplate): void
{
if (empty($themeBase)) {
return;
}

$this->theme = sprintf("%s::%s", $themeBase, "emails.theme");

$themedTemplate = sprintf("%s::%s", $themeBase, $markdownTemplate);

if (View::exists($themedTemplate)) {
$this->markdownTemplate = $themedTemplate;
} else {
if (!View::exists($markdownTemplate)) {
throw new \Exception("Unable to find any template corresponding to " . $markdownTemplate);
}
}
}
}
1 change: 1 addition & 0 deletions app/Models/PolydockStoreApp.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class PolydockStoreApp extends Model
'lagoon_remove_script',
'email_subject_line',
'email_body_markdown',
'mail_theme',
'status',
'uuid',
'available_for_trials',
Expand Down
72 changes: 72 additions & 0 deletions app/Providers/CustomTemplateProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Log;

class CustomTemplateProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
$templatePath = config('mail.custom_templates_path', storage_path('app/private/templates'));
$themes = [];

// Only register custom template path if directory exists
if (is_dir($templatePath)) {
$themes = $this->discoverThemes($templatePath);

foreach ($themes as $themeName => $themePath) {
$this->loadViewsFrom($themePath, $themeName);
Log::info('Custom theme registered', [
'theme' => $themeName,
'path' => $themePath,
]);
}

Log::info('Custom email themes loaded', [
'path' => $templatePath,
'themes_found' => count($themes),
'theme_names' => array_keys($themes)
]);
} else {
Log::debug('Custom templates directory not found', ['path' => $templatePath]);
}

// Make themes available globally via service container
$this->app->singleton('mail.themes', fn() => array_keys($themes));
}

/**
* Discover theme directories and return them as an array.
*
* @param string $templatePath
* @return array<string, string> Array of theme name => path
*/
private function discoverThemes(string $templatePath): array
{
$themes = [];

$directories = array_filter(
scandir($templatePath),
fn($item) => is_dir($templatePath . DIRECTORY_SEPARATOR . $item) && !str_starts_with($item, '.')
);

foreach ($directories as $dir) {
$themes[$dir] = $templatePath . DIRECTORY_SEPARATOR . $dir;
}

return $themes;
}

/**
* Bootstrap services.
*/
public function boot(): void
{
//
}
}
1 change: 1 addition & 0 deletions bootstrap/providers.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

return [
App\Providers\AppServiceProvider::class,
App\Providers\CustomTemplateProvider::class,
App\Providers\Filament\AdminPanelProvider::class,
App\Providers\Filament\AppPanelProvider::class,
App\Providers\HorizonServiceProvider::class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('polydock_store_apps', function (Blueprint $table) {
$table->string('mail_theme')->nullable()->after('name');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('polydock_store_apps', function (Blueprint $table) {
$table->dropColumn('mail_theme');
});
}
};
Loading
Loading