{{-- Flash messages rendered by the admin layout — no duplicate here. --}} {{-- Platform license banner — shown ONLY when the MailTrixy license file is missing. Fresh installs already have it from the installer's verifyLicense step; this banner is the recovery path for customers who upgraded an existing install without going through the installer. Once the customer submits a valid code here, every addon's license check flips true on the next render. --}} @if(empty($platformLicense))

{{ __('MailTrixy license is missing on this server.') }}

{{ __('Paste your Envato purchase code below. The same code unlocks every addon — you only need to do this once.') }}

@csrf
@else {{-- Quiet confirmation banner — same info without taking over the page once everything is in order. --}}

{{ __('MailTrixy licensed to') }} {{ $platformLicense['envato_buyer'] ?: __('Envato buyer') }} @if(!empty($platformLicense['verified_at'])) {{ __('on') }} {{ \Carbon\Carbon::parse($platformLicense['verified_at'])->toFormattedDateString() }} @endif — {{ __('every addon is unlockable.') }}

@endif {{-- Upload Panel — only visible when the platform license has been verified. Server-side AddonController::upload() also refuses unlicensed uploads, but hiding the form removes the affordance entirely so the admin isn't confused by an input they can't use. --}} @if(!empty($platformLicense))

{{ __('Install Addon') }}

{{ __('Upload a :brand addon ZIP. The package must contain an addon.json manifest at its root.', ['brand' => \App\Helpers\BrandingHelper::name()]) }}

@csrf
@error('addon_zip')

{{ $message }}

@enderror

{{ __('Max 50 MB. Only ZIP files containing an addon.json manifest are accepted. Uploading code runs with full application privileges — only install addons from sources you trust.') }}

@endif {{-- Installed Addons --}}

{{ __('Installed Addons') }}

{{ __('Activate an addon to register its service provider, run its migrations, and mount its routes. Deactivating leaves the files on disk but skips the boot step.') }}

@if($addons->isEmpty())

{{ __('No addons installed yet. Upload a ZIP above to get started.') }}

@else
@php // Every addon inherits its licensed state from the // platform-wide MailTrixy license. Hoisted out of // the loop because the value never changes per row. $__licensed = !empty($platformLicense); @endphp @foreach($addons as $addon)

{{ $addon->name }}

v{{ $addon->version }} @if($addon->isActive()) {{ __('Active') }} @else {{ __('Inactive') }} @endif
@if($addon->description)

{{ $addon->description }}

@endif
{{ __('Slug') }}: {{ $addon->slug }} @if($addon->author) {{ __('Author') }}: @if($addon->author_url) {{ $addon->author }} @else {{ $addon->author }} @endif @endif @if($addon->installed_at) {{ __('Installed') }}: {{ $addon->installed_at->diffForHumans() }} @endif
{{-- Action bar — Settings | Deactivate/Activate | Uninstall. Settings is a proper button (was a tiny text link below the description, which most users missed). It pulls from the addon's manifest `settings_url` so each addon can ship its own admin UI without touching the core sidebar. --}} @php // The manifest stores a root-relative path like // "/admin/addons/instagram/settings". Wrap it with // url() so it picks up APP_URL — installs running // out of a subfolder (e.g. https://example.com/ // mailtrixy/public/) would otherwise navigate to // https://example.com/admin/addons/… which is // OUTSIDE the MailTrixy install and 404s. $settingsUrl = $addon->manifest['settings_url'] ?? null; if ($settingsUrl) { $settingsUrl = url($settingsUrl); } // The addon's ServiceProvider only registers routes when // its files exist under addons/{slug}/. If a customer // restored a DB backup without re-uploading the addon // ZIP — or the disk failed — the row stays active but // the folder is gone. Rendering the Settings button // anyway sends them straight to a 404. Detect the // missing-files state up-front so we show a helpful // "Reinstall files" banner instead. $addonFolderExists = is_dir(base_path('addons/'.$addon->slug)); @endphp
@if(!$addonFolderExists) {{-- Files missing — no route was registered, so the Settings URL would 404. Replace the button with a clear status pill that links to the upload UI at the top of the page. --}} {{ __('Files missing — reinstall') }} @elseif($settingsUrl && $addon->isActive()) {{ __('Settings') }} @endif @if($addon->isActive())
@csrf
@elseif($__licensed) {{-- Licensed + inactive → Activate is enabled. --}}
@csrf
@else {{-- Unlicensed → Activate is disabled and we tell the admin to enter a code below. AddonController::activate ALSO rejects this server-side, so this is purely UX affordance. --}} @endif
@csrf @method('DELETE')
{{-- Per-row license affordance removed. The single platform-wide banner at the top of the page handles unlicensed installs; once that's verified, every addon row just shows the Activate button. --}}
@endforeach
@endif