refactor(file-upload): SSR-first state, click-to-replace, cleaner preview

- Server renders initial file-upload state (src, alt, label text,
  has-current class) so there is no flash of broken-image icon while
  JS boots. JS no longer duplicates that work — it only reacts to
  user input (select, clear, drag-and-drop) and uses data-current-src
  solely to restore the server file after a blob preview.
- Clicking the current preview image now opens the file dialog, mirrors
  the Replace button, with hover affordance (cursor + primary border).
- Drop the transparency checker pattern in the preview — the admin UI
  does not need Figma-style transparency semantics. Preview is now a
  flat theme-aware surface (--app-preview-bg) that keeps white or
  black logos visible against a neutral gray.
- Move tenant favicon out of the aside into the Master-data tab as its
  own details block next to the Tenant-logos section. Uses the same
  barrier-form pattern (HTML5 form attribute + app-file-upload.phtml)
  for a consistent instant-upload UX.
- Tenant-logo slot label is a native <label> element — picks up the
  global form-label typography and spacing, no custom muted-color
  class needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 20:53:58 +02:00
parent 6e3fc63c1d
commit e814b5fd11
10 changed files with 78 additions and 85 deletions

View File

@@ -235,6 +235,35 @@ $openOverrideCard = $detailsOpenAll;
</div>
</details>
<hr>
<details name="tenant-master-favicon" <?php e($detailsOpenAll ? 'open' : ''); ?>>
<summary><?php e(t('Tenant favicon')); ?></summary>
<hr>
<?php
$tenantFaviconUuid = (string) ($values['uuid'] ?? '');
$faviconPresent = $hasFavicon ?? false;
$fileUpload = [
'name' => 'favicon',
'accept' => 'image/png',
'hint' => t('Square PNG. Icons are center-cropped.'),
'currentSrc' => $faviconPresent && $tenantFaviconUuid !== ''
? asset('favicon/tenants/' . $tenantFaviconUuid . '/favicon/favicon-32x32.png')
: '',
'currentLabel' => t('Favicon'),
'formId' => 'tenant-favicon-form',
'deleteFormId' => $faviconPresent ? 'tenant-favicon-delete-form' : '',
'deleteConfirm' => t('Delete this favicon?'),
];
require templatePath('partials/app-file-upload.phtml');
?>
<?php if ($canUpdateTenant): ?>
<div class="tenant-logo-actions">
<button type="submit" form="tenant-favicon-form" class="secondary outline small">
<?php e(t('Save')); ?>
</button>
</div>
<?php endif; ?>
</details>
<hr>
<details name="tenant-master-contact" <?php e($detailsOpenAll ? 'open' : ''); ?>>
<summary><?php e(t('Contact')); ?></summary>
<hr>

View File

@@ -77,6 +77,14 @@ $hasFavicon = $tenantUuid !== '' && $tenantFaviconService->hasFavicon($tenantUui
<input type="hidden" name="theme" value="dark">
<?php Session::getCsrfInput(); ?>
</form>
<form id="tenant-favicon-form" method="post"
action="admin/tenants/favicon/<?php e($tenantUuid); ?>" enctype="multipart/form-data" hidden>
<?php Session::getCsrfInput(); ?>
</form>
<form id="tenant-favicon-delete-form" method="post"
action="admin/tenants/favicon-delete/<?php e($tenantUuid); ?>" hidden>
<?php Session::getCsrfInput(); ?>
</form>
<?php endif; ?>
<?php
@@ -112,35 +120,6 @@ $hasFavicon = $tenantUuid !== '' && $tenantFaviconService->hasFavicon($tenantUui
<p><?php e(t('Tenant')); ?></p>
</hgroup>
<hr>
<?php if ($canUpdateTenant): ?>
<details name="tenant-favicon">
<summary>
<?php e(t('Upload favicon')); ?>
</summary>
<hr>
<small><?php e(t('Square images are recommended (icons are center-cropped).')); ?></small>
<hr>
<form class="user-avatar-form" method="post" action="admin/tenants/favicon/<?php e($tenantUuid); ?>"
enctype="multipart/form-data">
<?php
$fileUpload = [
'name' => 'favicon',
'accept' => 'image/png',
'hint' => t('Allowed file types: PNG'),
'currentSrc' => $hasFavicon ? asset('favicon/tenants/' . $tenantUuid . '/favicon/favicon-32x32.png') : '',
'currentLabel' => t('Favicon'),
'deleteAction' => $hasFavicon ? 'admin/tenants/favicon-delete/' . $tenantUuid : '',
];
require templatePath('partials/app-file-upload.phtml');
?>
<button type="submit" class="app-action-success">
<?php e(t('Save')); ?>
</button>
<?php Session::getCsrfInput(); ?>
</form>
</details>
<?php endif; ?>
<hr>
<?php
$phone = trim((string) ($values['phone'] ?? ''));
$fax = trim((string) ($values['fax'] ?? ''));