Compare commits
8 Commits
bugfix/sav
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ef32a2b0c | |||
| 54d16eb3b8 | |||
| ae3264253d | |||
| 4cbfe52d5b | |||
| 27c6b017cd | |||
| 8378c33465 | |||
| c7c4e1194d | |||
| 3b367eff7f |
@@ -250,6 +250,7 @@ Manual testing workflow:
|
||||
- Eliminate manual property-by-property request→domain mapping drift (example: `CreateEnvelopeCommandHandler`) by introducing a standardized mapping policy (AutoMapper/profile conventions or reflection/source-generator based mapper) and enforcing it across envelope workflows; this needs to be solved in the shared company Core library level so all services get the same guardrails by default.
|
||||
- P1 security workstream: remove dynamic SQL string composition/interpolation from active save/create/update paths (`string.Format`, `$"...{input}..."`, `ToSqlParam`) and migrate to parameterized queries only; treat this as a potential SQL injection vulnerability (stability + security risk), execute repo-wide audit, and add CI guardrails to block reintroduction.
|
||||
- Alternative UX design (Server component): in `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeSenderPage.razor`, enforce deterministic row double-click behavior as exactly one action (preview OR edit), and keep row action buttons/tooltips aligned with the same rule to eliminate ambiguous navigation.
|
||||
- Implement SPA-style language switching so the UI culture updates immediately without a full page reload.
|
||||
|
||||
## Database
|
||||
|
||||
|
||||
@@ -276,4 +276,61 @@
|
||||
<data name="YesWithAttachment" xml:space="preserve">
|
||||
<value>Yes, with Attachment</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="ElectronicSignature" xml:space="preserve">
|
||||
<value>Electronic Signature</value>
|
||||
</data>
|
||||
<data name="EnvelopeType" xml:space="preserve">
|
||||
<value>Envelope Type</value>
|
||||
</data>
|
||||
<data name="Language" xml:space="preserve">
|
||||
<value>Language</value>
|
||||
</data>
|
||||
<data name="CertificationType" xml:space="preserve">
|
||||
<value>Certification</value>
|
||||
</data>
|
||||
<data name="TFAEnabled" xml:space="preserve">
|
||||
<value>2FA enabled</value>
|
||||
</data>
|
||||
<data name="UseAccessCode" xml:space="preserve">
|
||||
<value>Use Access Code</value>
|
||||
</data>
|
||||
<data name="FinalEmailToCreator" xml:space="preserve">
|
||||
<value>Final Email to Creator</value>
|
||||
</data>
|
||||
<data name="FinalEmailToReceivers" xml:space="preserve">
|
||||
<value>Final Email to Receivers</value>
|
||||
</data>
|
||||
<data name="SendReminderEmails" xml:space="preserve">
|
||||
<value>Send Reminder Emails</value>
|
||||
</data>
|
||||
<data name="FirstReminderDays" xml:space="preserve">
|
||||
<value>First Reminder (Days)</value>
|
||||
</data>
|
||||
<data name="ReminderIntervalDays" xml:space="preserve">
|
||||
<value>Reminder Interval (Days)</value>
|
||||
</data>
|
||||
<data name="ExpiresWhenDays" xml:space="preserve">
|
||||
<value>Expires After (Days)</value>
|
||||
</data>
|
||||
<data name="ExpiresWarningWhenDays" xml:space="preserve">
|
||||
<value>Expiry Warning (Days)</value>
|
||||
</data>
|
||||
<data name="SenderEditor_EnvelopeSettings" xml:space="preserve">
|
||||
<value>Envelope Settings</value>
|
||||
</data>
|
||||
<data name="SenderEditor_AdvancedOptional" xml:space="preserve">
|
||||
<value>Advanced</value>
|
||||
</data>
|
||||
<data name="SenderEditor_SelectTemplate" xml:space="preserve">
|
||||
<value>Select Template</value>
|
||||
</data>
|
||||
<data name="SenderEditor_Validation_ReminderInterval" xml:space="preserve">
|
||||
<value>Please enter a reminder interval greater than 0 days.</value>
|
||||
</data>
|
||||
<data name="SenderEditor_Validation_FirstReminder" xml:space="preserve">
|
||||
<value>Please enter valid days for the first reminder.</value>
|
||||
</data>
|
||||
<data name="SenderEditor_Validation_ExpiryWarning" xml:space="preserve">
|
||||
<value>The expiry warning cannot be after the expiry date.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -113,4 +113,61 @@
|
||||
<data name="MessageInvitationSent" xml:space="preserve">
|
||||
<value>Invitation envoyee</value>
|
||||
</data>
|
||||
<data name="ElectronicSignature" xml:space="preserve">
|
||||
<value>Signature electronique</value>
|
||||
</data>
|
||||
<data name="EnvelopeType" xml:space="preserve">
|
||||
<value>Type d'enveloppe</value>
|
||||
</data>
|
||||
<data name="Language" xml:space="preserve">
|
||||
<value>Langue</value>
|
||||
</data>
|
||||
<data name="CertificationType" xml:space="preserve">
|
||||
<value>Certification</value>
|
||||
</data>
|
||||
<data name="TFAEnabled" xml:space="preserve">
|
||||
<value>2FA active</value>
|
||||
</data>
|
||||
<data name="UseAccessCode" xml:space="preserve">
|
||||
<value>Utiliser un code d'acces</value>
|
||||
</data>
|
||||
<data name="FinalEmailToCreator" xml:space="preserve">
|
||||
<value>E-mail final au createur</value>
|
||||
</data>
|
||||
<data name="FinalEmailToReceivers" xml:space="preserve">
|
||||
<value>E-mail final aux destinataires</value>
|
||||
</data>
|
||||
<data name="SendReminderEmails" xml:space="preserve">
|
||||
<value>Envoyer des e-mails de rappel</value>
|
||||
</data>
|
||||
<data name="FirstReminderDays" xml:space="preserve">
|
||||
<value>Premier rappel (jours)</value>
|
||||
</data>
|
||||
<data name="ReminderIntervalDays" xml:space="preserve">
|
||||
<value>Intervalle de rappel (jours)</value>
|
||||
</data>
|
||||
<data name="ExpiresWhenDays" xml:space="preserve">
|
||||
<value>Expire apres (jours)</value>
|
||||
</data>
|
||||
<data name="ExpiresWarningWhenDays" xml:space="preserve">
|
||||
<value>Avertissement d'expiration (jours)</value>
|
||||
</data>
|
||||
<data name="SenderEditor_EnvelopeSettings" xml:space="preserve">
|
||||
<value>Parametres de l'enveloppe</value>
|
||||
</data>
|
||||
<data name="SenderEditor_AdvancedOptional" xml:space="preserve">
|
||||
<value>Avance</value>
|
||||
</data>
|
||||
<data name="SenderEditor_SelectTemplate" xml:space="preserve">
|
||||
<value>Selectionner un modele</value>
|
||||
</data>
|
||||
<data name="SenderEditor_Validation_ReminderInterval" xml:space="preserve">
|
||||
<value>Veuillez saisir un intervalle de rappel superieur a 0 jour.</value>
|
||||
</data>
|
||||
<data name="SenderEditor_Validation_FirstReminder" xml:space="preserve">
|
||||
<value>Veuillez saisir des jours valides pour le premier rappel.</value>
|
||||
</data>
|
||||
<data name="SenderEditor_Validation_ExpiryWarning" xml:space="preserve">
|
||||
<value>L'avertissement d'expiration ne peut pas etre apres la date d'expiration.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -279,4 +279,61 @@
|
||||
<data name="YesWithAttachment" xml:space="preserve">
|
||||
<value>Ja, mit Anhang</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="ElectronicSignature" xml:space="preserve">
|
||||
<value>Elektronische Signatur</value>
|
||||
</data>
|
||||
<data name="EnvelopeType" xml:space="preserve">
|
||||
<value>Umschlagtyp</value>
|
||||
</data>
|
||||
<data name="Language" xml:space="preserve">
|
||||
<value>Sprache</value>
|
||||
</data>
|
||||
<data name="CertificationType" xml:space="preserve">
|
||||
<value>Zertifizierung</value>
|
||||
</data>
|
||||
<data name="TFAEnabled" xml:space="preserve">
|
||||
<value>2FA aktiviert</value>
|
||||
</data>
|
||||
<data name="UseAccessCode" xml:space="preserve">
|
||||
<value>Zugriffscode verwenden</value>
|
||||
</data>
|
||||
<data name="FinalEmailToCreator" xml:space="preserve">
|
||||
<value>Finale E-Mail an Ersteller</value>
|
||||
</data>
|
||||
<data name="FinalEmailToReceivers" xml:space="preserve">
|
||||
<value>Finale E-Mail an Empfänger</value>
|
||||
</data>
|
||||
<data name="SendReminderEmails" xml:space="preserve">
|
||||
<value>Erinnerungs-E-Mails senden</value>
|
||||
</data>
|
||||
<data name="FirstReminderDays" xml:space="preserve">
|
||||
<value>Erste Erinnerung (Tage)</value>
|
||||
</data>
|
||||
<data name="ReminderIntervalDays" xml:space="preserve">
|
||||
<value>Erinnerungsintervall (Tage)</value>
|
||||
</data>
|
||||
<data name="ExpiresWhenDays" xml:space="preserve">
|
||||
<value>Läuft ab nach (Tage)</value>
|
||||
</data>
|
||||
<data name="ExpiresWarningWhenDays" xml:space="preserve">
|
||||
<value>Ablaufwarnung (Tage)</value>
|
||||
</data>
|
||||
<data name="SenderEditor_EnvelopeSettings" xml:space="preserve">
|
||||
<value>Envelope-Einstellungen</value>
|
||||
</data>
|
||||
<data name="SenderEditor_AdvancedOptional" xml:space="preserve">
|
||||
<value>Erweitert</value>
|
||||
</data>
|
||||
<data name="SenderEditor_SelectTemplate" xml:space="preserve">
|
||||
<value>Vorlage auswählen</value>
|
||||
</data>
|
||||
<data name="SenderEditor_Validation_ReminderInterval" xml:space="preserve">
|
||||
<value>Bitte geben Sie ein Erinnerungsintervall größer als 0 Tage ein.</value>
|
||||
</data>
|
||||
<data name="SenderEditor_Validation_FirstReminder" xml:space="preserve">
|
||||
<value>Bitte geben Sie gültige Tage für die erste Erinnerung ein.</value>
|
||||
</data>
|
||||
<data name="SenderEditor_Validation_ExpiryWarning" xml:space="preserve">
|
||||
<value>Die Ablaufwarnung darf nicht nach dem Ablaufdatum liegen.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
_selected = option;
|
||||
_isOpen = false;
|
||||
await LocalizationService.SetLanguageAsync(option.Code);
|
||||
Navigation.NavigateTo(Navigation.Uri, forceLoad: false);
|
||||
Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
|
||||
}
|
||||
|
||||
private sealed record LanguageOption(string Code, string FlagClass, string ShortName);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
@using EnvelopeGenerator.Application.Envelopes.Commands
|
||||
@using EnvelopeGenerator.Application.Resources
|
||||
@using EnvelopeGenerator.Domain.Constants
|
||||
@using EnvelopeGenerator.Domain.Resources
|
||||
@using EnvelopeGenerator.Server.Client.Components.PdfEditor
|
||||
@using EnvelopeGenerator.Server.Client.Services
|
||||
@using Microsoft.AspNetCore.WebUtilities
|
||||
@@ -22,6 +23,7 @@
|
||||
@inject EnvelopeTypeService EnvelopeTypeService
|
||||
@inject AuthService AuthService
|
||||
@inject IStringLocalizer<Resource> UiLocalizer
|
||||
@inject IStringLocalizer<Model> ModelLocalizer
|
||||
|
||||
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
||||
<link href="_content/DevExpress.Blazor.Viewer/css/dx-blazor-viewer-components.bs5.css" rel="stylesheet" />
|
||||
@@ -32,64 +34,52 @@
|
||||
|
||||
@* ── Action Bar ── *@
|
||||
<div class="envelope-action-bar">
|
||||
<div class="envelope-action-bar__inner"
|
||||
style="flex-direction: row; align-items: center; padding: 0.35rem 1.5rem; gap: 0.75rem;">
|
||||
<div class="envelope-action-bar__inner sender-editor-action-inner">
|
||||
|
||||
@* Left: Title + meta inputs + receivers *@
|
||||
<div style="flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: stretch; gap: 0.5rem;">
|
||||
<div class="sender-editor-main-column">
|
||||
|
||||
@* ── Title & Message inputs ── *@
|
||||
<div style="display: flex; align-items: flex-start; gap: 0.5rem; flex-wrap: wrap;">
|
||||
<div class="sender-editor-meta-row">
|
||||
|
||||
@* Titel *@
|
||||
<div style="display: flex; flex-direction: column; gap: 0.2rem; flex: 1 1 180px; min-width: 140px;">
|
||||
<label style="font-size: 0.65rem; font-weight: 600; color: #6b7280;
|
||||
text-transform: uppercase; letter-spacing: 0.05em;">
|
||||
@L("Title", "Titel") <span style="color:#dc2626;">*</span>
|
||||
<div class="sender-editor-input-field sender-editor-input-field--title">
|
||||
<label class="sender-editor-label">
|
||||
@L("Title", "Titel") <span class="sender-editor-required-asterisk">*</span>
|
||||
</label>
|
||||
<input type="text"
|
||||
value="@_envelopeTitle"
|
||||
@oninput="e => _envelopeTitle = e.Value?.ToString() ?? string.Empty"
|
||||
placeholder='@L("SenderEditor_Title_Placeholder", "z.B. Arbeitsvertrag 2025")'
|
||||
maxlength="120"
|
||||
style="font-size: 0.78rem; font-weight: 500; color: #1f2937;
|
||||
border: 1px solid #d1d5db; border-radius: 5px;
|
||||
padding: 0.3rem 0.55rem; outline: none; width: 100%;
|
||||
background: #fff;
|
||||
@(_titleTouched && string.IsNullOrWhiteSpace(_envelopeTitle) ? "border-color:#dc2626;" : "")" />
|
||||
class="sender-editor-text-input @(_titleTouched && string.IsNullOrWhiteSpace(_envelopeTitle) ? "sender-editor-text-input--invalid" : string.Empty)" />
|
||||
@if (_titleTouched && string.IsNullOrWhiteSpace(_envelopeTitle))
|
||||
{
|
||||
<span style="font-size:0.6rem;color:#dc2626;">@L("RequiredField", "Pflichtfeld")</span>
|
||||
<span class="sender-editor-required-text">@L("RequiredField", "Pflichtfeld")</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@* Nachricht *@
|
||||
<div style="display: flex; flex-direction: column; gap: 0.2rem; flex: 2 1 260px; min-width: 180px;">
|
||||
<label style="font-size: 0.65rem; font-weight: 600; color: #6b7280;
|
||||
text-transform: uppercase; letter-spacing: 0.05em;">
|
||||
@L("Message", "Nachricht") <span style="color:#6b7280;font-weight:400;">(@L("Optional", "optional"))</span>
|
||||
<div class="sender-editor-input-field sender-editor-input-field--message">
|
||||
<label class="sender-editor-label">
|
||||
@L("Message", "Nachricht") <span class="sender-editor-optional">(@L("Optional", "optional"))</span>
|
||||
</label>
|
||||
<input type="text"
|
||||
value="@_envelopeMessage"
|
||||
@oninput="e => _envelopeMessage = e.Value?.ToString() ?? string.Empty"
|
||||
placeholder='@L("SenderEditor_Message_Placeholder", "Bitte unterzeichnen Sie das beigefügte Dokument.")'
|
||||
maxlength="500"
|
||||
style="font-size: 0.78rem; color: #1f2937;
|
||||
border: 1px solid #d1d5db; border-radius: 5px;
|
||||
padding: 0.3rem 0.55rem; outline: none; width: 100%;
|
||||
background: #fff;" />
|
||||
class="sender-editor-text-input" />
|
||||
</div>
|
||||
|
||||
@* PDF filename badge *@
|
||||
@if (_pdfLoaded)
|
||||
{
|
||||
<div style="display:flex;align-items:flex-end;gap:0.4rem;padding-bottom:0.05rem;flex-shrink:0;">
|
||||
<span style="font-size: 0.7rem; color: #6b7280;">@_fileName</span>
|
||||
<div class="sender-editor-file-meta">
|
||||
<span class="sender-editor-file-name">@_fileName</span>
|
||||
@if (_signatureFields.Count > 0)
|
||||
{
|
||||
<span style="display: inline-flex; align-items: center; padding: 0.125rem 0.4rem;
|
||||
background: #ede9fe; border-radius: 0.25rem; color: #6d28d9;
|
||||
font-weight: 500; font-size: 0.7rem; white-space: nowrap;">
|
||||
<span class="sender-editor-signature-count">
|
||||
@_signatureFields.Count @L("SenderEditor_SignatureField", "Signaturfeld")@(_signatureFields.Count != 1 ? "er" : "")
|
||||
</span>
|
||||
}
|
||||
@@ -97,106 +87,107 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
<details style="border:1px solid #dbe5ef;border-radius:8px;background:#f9fbfd;">
|
||||
<summary style="cursor:pointer;list-style:none;display:flex;align-items:center;justify-content:space-between;padding:0.55rem 0.7rem;font-size:0.74rem;font-weight:700;color:#1f2937;text-transform:uppercase;letter-spacing:0.04em;">
|
||||
<span>@L("SenderEditor_EnvelopeSettings", "Envelope-Einstellungen")</span>
|
||||
<span style="font-size:0.66rem;color:#64748b;font-weight:600;">@L("SenderEditor_AdvancedOptional", "Erweitert")</span>
|
||||
</summary>
|
||||
<div class="sender-settings-shell">
|
||||
<DxAccordion ExpandMode="AccordionExpandMode.SingleOrNone" CssClass="sender-settings-accordion">
|
||||
<Items>
|
||||
<DxAccordionItem Text='@L("SenderEditor_EnvelopeSettings", "Envelope-Einstellungen")' Expanded="false">
|
||||
<ContentTemplate>
|
||||
<div class="sender-settings-body">
|
||||
<div class="sender-settings-grid">
|
||||
<div class="sender-settings-field">
|
||||
<label class="sender-settings-label">@L("EnvelopeType", "Umschlagtyp")</label>
|
||||
<select value="@(_selectedEnvelopeTypeId?.ToString() ?? string.Empty)"
|
||||
@onchange="OnEnvelopeTypeChanged"
|
||||
class="sender-settings-select">
|
||||
<option value="">@L("SenderEditor_SelectTemplate", "Vorlage auswählen")</option>
|
||||
@foreach (var type in _envelopeTypes)
|
||||
{
|
||||
<option value="@type.Id">@type.Title</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style="padding:0 0.7rem 0.7rem;display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:0.45rem;">
|
||||
<div style="display:flex;flex-direction:column;gap:0.2rem;">
|
||||
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("EnvelopeType", "Umschlagtyp")</label>
|
||||
<select value="@(_selectedEnvelopeTypeId?.ToString() ?? string.Empty)"
|
||||
@onchange="OnEnvelopeTypeChanged"
|
||||
style="font-size:0.78rem;border:1px solid #d1d5db;border-radius:5px;padding:0.3rem 0.45rem;background:#fff;">
|
||||
<option value="">@L("SenderEditor_SelectTemplate", "Vorlage auswählen")</option>
|
||||
@foreach (var type in _envelopeTypes)
|
||||
{
|
||||
<option value="@type.Id">@type.Title</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="sender-settings-field">
|
||||
<label class="sender-settings-label">@L("Language", "Sprache")</label>
|
||||
<select @bind="_language" class="sender-settings-select">
|
||||
<option value="de">Deutsch (de)</option>
|
||||
<option value="en">English (en)</option>
|
||||
<option value="de-DE">Deutsch (de-DE)</option>
|
||||
<option value="en-US">English (en-US)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:0.2rem;">
|
||||
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("Language", "Sprache")</label>
|
||||
<select @bind="_language"
|
||||
style="font-size:0.78rem;border:1px solid #d1d5db;border-radius:5px;padding:0.3rem 0.45rem;background:#fff;">
|
||||
<option value="de">Deutsch (de)</option>
|
||||
<option value="en">English (en)</option>
|
||||
<option value="de-DE">Deutsch (de-DE)</option>
|
||||
<option value="en-US">English (en-US)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="sender-settings-field">
|
||||
<label class="sender-settings-label">@L("CertificationType", "Zertifizierung")</label>
|
||||
<select @bind="_certificationType" class="sender-settings-select">
|
||||
<option value="1">@L("AdvancedElectronicSignature", "Advanced Electronic Signature")</option>
|
||||
<option value="2">@L("ElectronicSignature", "Electronic Signature")</option>
|
||||
<option value="3">@L("QualifiedSignature", "Qualified Signature")</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:0.2rem;">
|
||||
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("CertificationType", "Zertifizierung")</label>
|
||||
<select @bind="_certificationType"
|
||||
style="font-size:0.78rem;border:1px solid #d1d5db;border-radius:5px;padding:0.3rem 0.45rem;background:#fff;">
|
||||
<option value="1">@L("AdvancedElectronicSignature", "Advanced Electronic Signature")</option>
|
||||
<option value="2">@L("ElectronicSignature", "Electronic Signature")</option>
|
||||
<option value="3">@L("QualifiedSignature", "Qualified Signature")</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="sender-settings-toggles">
|
||||
<label class="sender-settings-toggle">
|
||||
<input type="checkbox" checked="@_tfaEnabled" @onchange="OnTfaChanged" />
|
||||
@L("TFAEnabled", "2FA aktiviert")
|
||||
</label>
|
||||
<label class="sender-settings-toggle">
|
||||
<input type="checkbox" checked="@_useAccessCode" @onchange="OnUseAccessCodeChanged" disabled="@_tfaEnabled" />
|
||||
@L("UseAccessCode", "Zugriffscode verwenden")
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:0.3rem;justify-content:flex-end;padding-bottom:0.25rem;">
|
||||
<label style="display:flex;align-items:center;gap:0.45rem;font-size:0.74rem;color:#1f2937;">
|
||||
<input type="checkbox" checked="@_tfaEnabled" @onchange="OnTfaChanged" />
|
||||
@L("TFAEnabled", "2FA aktiviert")
|
||||
</label>
|
||||
<label style="display:flex;align-items:center;gap:0.45rem;font-size:0.74rem;color:#1f2937;">
|
||||
<input type="checkbox" checked="@_useAccessCode" @onchange="OnUseAccessCodeChanged" disabled="@_tfaEnabled" />
|
||||
@L("UseAccessCode", "Zugriffscode verwenden")
|
||||
</label>
|
||||
</div>
|
||||
<div class="sender-settings-field">
|
||||
<label class="sender-settings-label">@L("FinalEmailToCreator", "Finale E-Mail an Ersteller")</label>
|
||||
<select @bind="_finalEmailToCreator" class="sender-settings-select">
|
||||
<option value="0">@L("No", "Nein")</option>
|
||||
<option value="1">@L("Yes", "Ja")</option>
|
||||
<option value="2">@L("YesWithAttachment", "Ja, mit Anhang")</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:0.2rem;">
|
||||
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("FinalEmailToCreator", "Finale E-Mail an Ersteller")</label>
|
||||
<select @bind="_finalEmailToCreator"
|
||||
style="font-size:0.78rem;border:1px solid #d1d5db;border-radius:5px;padding:0.3rem 0.45rem;background:#fff;">
|
||||
<option value="0">@L("No", "Nein")</option>
|
||||
<option value="1">@L("Yes", "Ja")</option>
|
||||
<option value="2">@L("YesWithAttachment", "Ja, mit Anhang")</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="sender-settings-field">
|
||||
<label class="sender-settings-label">@L("FinalEmailToReceivers", "Finale E-Mail an Empfänger")</label>
|
||||
<select @bind="_finalEmailToReceivers" class="sender-settings-select">
|
||||
<option value="0">@L("No", "Nein")</option>
|
||||
<option value="1">@L("Yes", "Ja")</option>
|
||||
<option value="2">@L("YesWithAttachment", "Ja, mit Anhang")</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:0.2rem;">
|
||||
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("FinalEmailToReceivers", "Finale E-Mail an Empfänger")</label>
|
||||
<select @bind="_finalEmailToReceivers"
|
||||
style="font-size:0.78rem;border:1px solid #d1d5db;border-radius:5px;padding:0.3rem 0.45rem;background:#fff;">
|
||||
<option value="0">@L("No", "Nein")</option>
|
||||
<option value="1">@L("Yes", "Ja")</option>
|
||||
<option value="2">@L("YesWithAttachment", "Ja, mit Anhang")</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="sender-settings-toggles sender-settings-toggles--single">
|
||||
<label class="sender-settings-toggle">
|
||||
<input type="checkbox" checked="@_sendReminderEmails" @onchange="OnSendReminderEmailsChanged" />
|
||||
@L("SendReminderEmails", "Erinnerungs-E-Mails senden")
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:0.3rem;justify-content:flex-end;padding-bottom:0.25rem;">
|
||||
<label style="display:flex;align-items:center;gap:0.45rem;font-size:0.74rem;color:#1f2937;">
|
||||
<input type="checkbox" checked="@_sendReminderEmails" @onchange="OnSendReminderEmailsChanged" />
|
||||
@L("SendReminderEmails", "Erinnerungs-E-Mails senden")
|
||||
</label>
|
||||
</div>
|
||||
<div class="sender-settings-field">
|
||||
<label class="sender-settings-label">@L("FirstReminderDays", "Erste Erinnerung (Tage)")</label>
|
||||
<InputNumber @bind-Value="_firstReminderDays" class="form-control form-control-sm sender-settings-number" disabled="@(!_sendReminderEmails)" />
|
||||
</div>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:0.2rem;">
|
||||
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("FirstReminderDays", "Erste Erinnerung (Tage)")</label>
|
||||
<InputNumber @bind-Value="_firstReminderDays" class="form-control form-control-sm" disabled="@(!_sendReminderEmails)" />
|
||||
</div>
|
||||
<div class="sender-settings-field">
|
||||
<label class="sender-settings-label">@L("ReminderIntervalDays", "Erinnerungsintervall (Tage)")</label>
|
||||
<InputNumber @bind-Value="_reminderIntervalDays" class="form-control form-control-sm sender-settings-number" disabled="@(!_sendReminderEmails)" />
|
||||
</div>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:0.2rem;">
|
||||
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("ReminderIntervalDays", "Erinnerungsintervall (Tage)")</label>
|
||||
<InputNumber @bind-Value="_reminderIntervalDays" class="form-control form-control-sm" disabled="@(!_sendReminderEmails)" />
|
||||
</div>
|
||||
<div class="sender-settings-field">
|
||||
<label class="sender-settings-label">@L("ExpiresWhenDays", "Läuft ab nach (Tage)")</label>
|
||||
<InputNumber @bind-Value="_expiresWhenDays" class="form-control form-control-sm sender-settings-number" />
|
||||
</div>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:0.2rem;">
|
||||
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("ExpiresWhenDays", "Läuft ab nach (Tage)")</label>
|
||||
<InputNumber @bind-Value="_expiresWhenDays" class="form-control form-control-sm" />
|
||||
</div>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:0.2rem;">
|
||||
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("ExpiresWarningWhenDays", "Ablaufwarnung (Tage)")</label>
|
||||
<InputNumber @bind-Value="_expiresWarningWhenDays" class="form-control form-control-sm" />
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<div class="sender-settings-field">
|
||||
<label class="sender-settings-label">@L("ExpiresWarningWhenDays", "Ablaufwarnung (Tage)")</label>
|
||||
<InputNumber @bind-Value="_expiresWarningWhenDays" class="form-control form-control-sm sender-settings-number" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ContentTemplate>
|
||||
</DxAccordionItem>
|
||||
</Items>
|
||||
</DxAccordion>
|
||||
</div>
|
||||
|
||||
<div class="sender-receivers-panel">
|
||||
<div class="sender-receivers-panel__header">
|
||||
@@ -226,8 +217,7 @@
|
||||
<div class="sender-receivers-list">
|
||||
@foreach (var receiver in _receivers)
|
||||
{
|
||||
<div class="sender-receiver-chip"
|
||||
style="border-left: 3px solid @receiver.Color;">
|
||||
<div class="sender-receiver-chip" style="--receiver-color:@receiver.Color;">
|
||||
<div class="sender-receiver-chip__body">
|
||||
<div class="sender-receiver-chip__name">@receiver.FullName</div>
|
||||
<div class="sender-receiver-chip__email">@receiver.Email</div>
|
||||
@@ -237,11 +227,11 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--signature-change sender-toolbar-action-btn sender-toolbar-action-btn--compact
|
||||
@(_pendingReceiverForPlacement?.Id == receiver.Id ? "pdf-toolbar__btn--signature-change-active" : "")"
|
||||
@onclick="() => ActivatePlacementForReceiver(receiver)"
|
||||
style="@(_pendingReceiverForPlacement?.Id == receiver.Id ? $"background:{receiver.Color};color:#fff;border-color:{receiver.Color};" : $"color:{receiver.Color};border-color:{receiver.Color};")"
|
||||
title='@L("SenderEditor_PlaceSignatureField", "Signaturfeld platzieren")'>
|
||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--signature-change sender-toolbar-action-btn sender-toolbar-action-btn--compact sender-receiver-chip__place-btn
|
||||
@(_pendingReceiverForPlacement?.Id == receiver.Id ? "sender-receiver-chip__place-btn--active" : "sender-receiver-chip__place-btn--idle")"
|
||||
@onclick="() => ActivatePlacementForReceiver(receiver)"
|
||||
style="--receiver-color:@receiver.Color;"
|
||||
title='@L("SenderEditor_PlaceSignatureField", "Signaturfeld platzieren")'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10z" />
|
||||
</svg>
|
||||
@@ -255,7 +245,7 @@
|
||||
</div>
|
||||
|
||||
@* Right: Buttons *@
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0;">
|
||||
<div class="sender-editor-actions">
|
||||
|
||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--reset"
|
||||
@onclick="Cancel"
|
||||
@@ -264,14 +254,12 @@
|
||||
</button>
|
||||
|
||||
@* PDF Upload *@
|
||||
<label class="pdf-toolbar__btn"
|
||||
<label class="pdf-toolbar__btn sender-editor-upload-btn @((!_isPageReady || _isPdfUploadInProgress) ? "sender-editor-upload-btn--disabled" : string.Empty)"
|
||||
title='@L("UploadPdf", "PDF hochladen")'
|
||||
style="cursor: @((_isPageReady && !_isPdfUploadInProgress) ? "pointer" : "not-allowed");
|
||||
opacity:@((_isPageReady && !_isPdfUploadInProgress) ? "1" : "0.6");
|
||||
display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.3rem 0.75rem; font-size: 0.75rem; font-weight: 500;">
|
||||
>
|
||||
@if (_isPdfUploadInProgress)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm" role="status" style="width:0.8rem;height:0.8rem;border-width:2px;"></span>
|
||||
<span class="spinner-border spinner-border-sm sender-editor-spinner" role="status"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -299,7 +287,7 @@
|
||||
<InputFile OnChange="OnPdfFileSelectedAsync"
|
||||
accept=".pdf"
|
||||
disabled="@(!_isPageReady || _isPdfUploadInProgress)"
|
||||
style="display: none;" />
|
||||
class="sender-editor-hidden-input" />
|
||||
</label>
|
||||
|
||||
@if (_pdfLoaded && _signatureFields.Count > 0)
|
||||
@@ -320,8 +308,7 @@
|
||||
title='@L("Save", "Speichern")'>
|
||||
@if (_isSaving)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-1" role="status"
|
||||
style="width:0.8rem;height:0.8rem;border-width:2px;"></span>
|
||||
<span class="spinner-border spinner-border-sm me-1 sender-editor-spinner" role="status"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -336,12 +323,10 @@
|
||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--success"
|
||||
@onclick="SendAsync"
|
||||
disabled="@_isSaving"
|
||||
title='@L("SendEnvelope", "Umschlag senden")'
|
||||
style="background: linear-gradient(135deg,#059669,#047857); color:#fff; border-radius:6px; padding:0.3rem 0.75rem; font-size:0.75rem; font-weight:600; border:none;">
|
||||
title='@L("SendEnvelope", "Umschlag senden")'>
|
||||
@if (_isSaving)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-1" role="status"
|
||||
style="width:0.8rem;height:0.8rem;border-width:2px;"></span>
|
||||
<span class="spinner-border spinner-border-sm me-1 sender-editor-spinner" role="status"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -357,11 +342,9 @@
|
||||
@* Placement mode hint bar *@
|
||||
@if (_pendingReceiverForPlacement is not null)
|
||||
{
|
||||
<div style="background: @_pendingReceiverForPlacement.Color; color: white; font-size: 0.75rem; font-weight: 500;
|
||||
padding: 0.3rem 1.5rem; text-align: center; letter-spacing: 0.01em;">
|
||||
<div class="sender-editor-placement-hint" style="--placement-color:@_pendingReceiverForPlacement.Color;">
|
||||
📌 @L("SenderEditor_ClickDocumentFor", "Klicken Sie auf die Stelle im Dokument für") <strong>@_pendingReceiverForPlacement.FullName</strong>.
|
||||
<button @onclick="CancelPlacement"
|
||||
style="background:none;border:none;color:white;cursor:pointer;font-size:0.75rem;text-decoration:underline;padding:0;">
|
||||
<button @onclick="CancelPlacement" class="sender-editor-placement-cancel">
|
||||
@L("Cancel", "Abbrechen")
|
||||
</button>
|
||||
</div>
|
||||
@@ -369,30 +352,25 @@
|
||||
</div>
|
||||
|
||||
@* ── Content ── *@
|
||||
<div class="envelope-content" style="padding: 0; overflow: hidden;">
|
||||
<div class="envelope-content sender-editor-content">
|
||||
@if (!_pdfLoaded)
|
||||
{
|
||||
@* Empty state *@
|
||||
<div class="d-flex justify-content-center align-items-center h-100">
|
||||
<div class="text-center" style="max-width: 420px;">
|
||||
<div style="width: 72px; height: 72px; background: rgba(255,255,255,0.15);
|
||||
border-radius: 50%; display: flex; align-items: center;
|
||||
justify-content: center; margin: 0 auto 1.25rem;">
|
||||
<div class="text-center sender-editor-empty-state">
|
||||
<div class="sender-editor-empty-state__icon-wrap">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" fill="white" viewBox="0 0 16 16">
|
||||
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h5 class="text-white mb-2">@L("SenderEditor_NoDocumentLoaded", "Kein Dokument geladen")</h5>
|
||||
<p class="text-white mb-4" style="opacity: 0.75; font-size: 0.85rem;">
|
||||
<p class="text-white mb-4 sender-editor-empty-state__description">
|
||||
@L("SenderEditor_UploadPdfToPlaceFields", "Laden Sie eine PDF-Datei hoch, um Signaturfelder zu platzieren.")
|
||||
</p>
|
||||
<label class="btn btn-light btn-sm"
|
||||
style="cursor: @((_isPageReady && !_isPdfUploadInProgress) ? "pointer" : "not-allowed");
|
||||
opacity:@((_isPageReady && !_isPdfUploadInProgress) ? "1" : "0.8");
|
||||
font-weight: 500;">
|
||||
<label class="btn btn-light btn-sm sender-editor-empty-state__upload @((!_isPageReady || _isPdfUploadInProgress) ? "sender-editor-empty-state__upload--disabled" : string.Empty)">
|
||||
@if (_isPdfUploadInProgress)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-1" role="status" style="width:0.8rem;height:0.8rem;border-width:2px;"></span>
|
||||
<span class="spinner-border spinner-border-sm me-1 sender-editor-spinner" role="status"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -420,7 +398,7 @@
|
||||
<InputFile OnChange="OnPdfFileSelectedAsync"
|
||||
accept=".pdf"
|
||||
disabled="@(!_isPageReady || _isPdfUploadInProgress)"
|
||||
style="display: none;" />
|
||||
class="sender-editor-hidden-input" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -544,18 +522,17 @@
|
||||
<BodyContentTemplate>
|
||||
@if (_saveErrorMessage is null)
|
||||
{
|
||||
<div style="display:flex;align-items:flex-start;gap:1rem;padding:0.5rem 0;">
|
||||
<div style="flex-shrink:0;width:40px;height:40px;background:#d1fae5;border-radius:50%;
|
||||
display:flex;align-items:center;justify-content:center;">
|
||||
<div class="sender-editor-save-result">
|
||||
<div class="sender-editor-save-result__icon sender-editor-save-result__icon--success">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#065f46" viewBox="0 0 16 16">
|
||||
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p style="margin:0 0 0.4rem;font-weight:600;color:#1f2937;font-size:0.95rem;">
|
||||
<p class="sender-editor-save-result__title">
|
||||
@_saveSuccessTitle
|
||||
</p>
|
||||
<p style="margin:0;color:#6b7280;font-size:0.85rem;line-height:1.5;">
|
||||
<p class="sender-editor-save-result__message">
|
||||
@_saveSuccessBody
|
||||
</p>
|
||||
</div>
|
||||
@@ -563,19 +540,18 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div style="display:flex;align-items:flex-start;gap:1rem;padding:0.5rem 0;">
|
||||
<div style="flex-shrink:0;width:40px;height:40px;background:#fee2e2;border-radius:50%;
|
||||
display:flex;align-items:center;justify-content:center;">
|
||||
<div class="sender-editor-save-result">
|
||||
<div class="sender-editor-save-result__icon sender-editor-save-result__icon--error">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#991b1b" viewBox="0 0 16 16">
|
||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
||||
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p style="margin:0 0 0.4rem;font-weight:600;color:#1f2937;font-size:0.95rem;">
|
||||
<p class="sender-editor-save-result__title">
|
||||
@L("SenderEditor_ErrorCreatingEnvelope", "Fehler beim Erstellen des Umschlags")
|
||||
</p>
|
||||
<p style="margin:0;color:#6b7280;font-size:0.85rem;line-height:1.5;word-break:break-word;">
|
||||
<p class="sender-editor-save-result__message sender-editor-save-result__message--break">
|
||||
@_saveErrorMessage
|
||||
</p>
|
||||
</div>
|
||||
@@ -583,30 +559,25 @@
|
||||
}
|
||||
</BodyContentTemplate>
|
||||
<FooterContentTemplate>
|
||||
<div style="display:flex;gap:0.5rem;justify-content:flex-end;width:100%;padding:0.5rem 0;">
|
||||
<div class="sender-editor-save-actions">
|
||||
@if (_saveErrorMessage is null)
|
||||
{
|
||||
<button class="btn btn-outline-secondary"
|
||||
@onclick="() => _savePopupVisible = false"
|
||||
style="border-radius:6px;padding:0.5rem 1.25rem;font-weight:500;">
|
||||
<button class="btn btn-outline-secondary sender-editor-save-actions__secondary"
|
||||
@onclick="() => _savePopupVisible = false">
|
||||
@L("Close", "Schließen")
|
||||
</button>
|
||||
|
||||
@if (_saveNavigateToDashboard)
|
||||
{
|
||||
<button class="btn btn-primary"
|
||||
@onclick="GoToDashboard"
|
||||
style="background:linear-gradient(135deg,#059669,#047857);border:none;border-radius:6px;
|
||||
padding:0.5rem 1.5rem;font-weight:600;color:#fff;">
|
||||
<button class="btn btn-primary sender-editor-save-actions__primary" @onclick="GoToDashboard">
|
||||
@L("SenderEditor_ToOverview", "Zur Übersicht")
|
||||
</button>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="btn btn-outline-secondary"
|
||||
@onclick="() => _savePopupVisible = false"
|
||||
style="border-radius:6px;padding:0.5rem 1.25rem;font-weight:500;">
|
||||
<button class="btn btn-outline-secondary sender-editor-save-actions__secondary"
|
||||
@onclick="() => _savePopupVisible = false">
|
||||
@L("Close", "Schließen")
|
||||
</button>
|
||||
}
|
||||
@@ -1586,7 +1557,11 @@
|
||||
|
||||
string L(string key, string fallback)
|
||||
{
|
||||
var value = UiLocalizer[key];
|
||||
return value.ResourceNotFound ? fallback : value.Value;
|
||||
var modelValue = ModelLocalizer[key];
|
||||
if (!modelValue.ResourceNotFound)
|
||||
return modelValue.Value;
|
||||
|
||||
var uiValue = UiLocalizer[key];
|
||||
return uiValue.ResourceNotFound ? fallback : uiValue.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
@inject EnvelopeGenerator.Server.Client.Services.ThemeService ThemeService
|
||||
@inject EnvelopeGenerator.Server.Client.Services.UserPreferencesService UserPreferencesService
|
||||
@inject EnvelopeGenerator.Server.Client.Services.CustomImagesService CustomImagesService
|
||||
@inject ILogger<EnvelopeSenderPage> Logger
|
||||
@inject ILogger<SenderPage> Logger
|
||||
@inject NavigationManager Navigation
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject AppVersionService AppVersion
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1002,6 +1002,108 @@ body.resizing {
|
||||
border: 1px solid rgba(126, 34, 206, 0.12);
|
||||
}
|
||||
|
||||
.sender-settings-shell {
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sender-settings-accordion {
|
||||
border: 1px solid #dbe5ef;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8fbff 100%);
|
||||
}
|
||||
|
||||
.sender-settings-accordion .dxbl-accordion-item {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.sender-settings-accordion .dxbl-accordion-item-header {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.66rem;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
padding: 0.38rem 0.62rem;
|
||||
background: rgba(124, 58, 237, 0.04);
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.sender-settings-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.65rem;
|
||||
padding: 0.25rem 0.7rem 0.7rem;
|
||||
}
|
||||
|
||||
.sender-settings-chip {
|
||||
align-self: flex-start;
|
||||
font-size: 0.66rem;
|
||||
font-weight: 700;
|
||||
color: #5b21b6;
|
||||
background: rgba(126, 34, 206, 0.1);
|
||||
border: 1px solid rgba(126, 34, 206, 0.15);
|
||||
border-radius: 999px;
|
||||
padding: 0.12rem 0.5rem;
|
||||
}
|
||||
|
||||
.sender-settings-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.sender-settings-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.sender-settings-label {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 700;
|
||||
color: #64748b;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.sender-settings-select,
|
||||
.sender-settings-number {
|
||||
font-size: 0.78rem;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
padding: 0.3rem 0.45rem;
|
||||
background: #fff;
|
||||
min-height: 32px;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.sender-settings-number:focus,
|
||||
.sender-settings-select:focus {
|
||||
border-color: rgba(126, 34, 206, 0.45);
|
||||
box-shadow: 0 0 0 2px rgba(126, 34, 206, 0.12);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.sender-settings-toggles {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
gap: 0.3rem;
|
||||
padding-bottom: 0.1rem;
|
||||
}
|
||||
|
||||
.sender-settings-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
font-size: 0.74rem;
|
||||
color: #1f2937;
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.sender-settings-toggles--single {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sender-receivers-panel__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1119,9 +1221,275 @@ body.resizing {
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
border: 1px solid rgba(126, 34, 206, 0.12);
|
||||
border-left: 3px solid var(--receiver-color, #7c3aed);
|
||||
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.sender-receiver-chip__place-btn--idle {
|
||||
color: var(--receiver-color, #7c3aed);
|
||||
border-color: var(--receiver-color, #7c3aed);
|
||||
}
|
||||
|
||||
.sender-receiver-chip__place-btn--active {
|
||||
background: var(--receiver-color, #7c3aed);
|
||||
color: #ffffff;
|
||||
border-color: var(--receiver-color, #7c3aed);
|
||||
}
|
||||
|
||||
.sender-editor-action-inner {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0.35rem 1.5rem;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.sender-editor-main-column {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.sender-editor-meta-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sender-editor-input-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.sender-editor-input-field--title {
|
||||
flex: 1 1 180px;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.sender-editor-input-field--message {
|
||||
flex: 2 1 260px;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.sender-editor-label {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
color: #6b7280;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.sender-editor-required-asterisk {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.sender-editor-optional {
|
||||
color: #6b7280;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.sender-editor-text-input {
|
||||
font-size: 0.78rem;
|
||||
color: #1f2937;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 5px;
|
||||
padding: 0.3rem 0.55rem;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.sender-editor-text-input--invalid {
|
||||
border-color: #dc2626;
|
||||
}
|
||||
|
||||
.sender-editor-required-text {
|
||||
font-size: 0.6rem;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.sender-editor-file-meta {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 0.4rem;
|
||||
padding-bottom: 0.05rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sender-editor-file-name {
|
||||
font-size: 0.7rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.sender-editor-signature-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.125rem 0.4rem;
|
||||
background: #ede9fe;
|
||||
border-radius: 0.25rem;
|
||||
color: #6d28d9;
|
||||
font-weight: 500;
|
||||
font-size: 0.7rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sender-editor-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sender-editor-upload-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
padding: 0.3rem 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sender-editor-upload-btn--disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.sender-editor-hidden-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sender-editor-spinner {
|
||||
width: 0.8rem;
|
||||
height: 0.8rem;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.sender-editor-placement-hint {
|
||||
background: var(--placement-color, #7c3aed);
|
||||
color: #ffffff;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
padding: 0.3rem 1.5rem;
|
||||
text-align: center;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.sender-editor-placement-cancel {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
font-size: 0.75rem;
|
||||
text-decoration: underline;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sender-editor-content {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sender-editor-empty-state {
|
||||
max-width: 420px;
|
||||
}
|
||||
|
||||
.sender-editor-empty-state__icon-wrap {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 1.25rem;
|
||||
}
|
||||
|
||||
.sender-editor-empty-state__description {
|
||||
opacity: 0.75;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.sender-editor-empty-state__upload {
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sender-editor-empty-state__upload--disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.sender-editor-save-result {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.sender-editor-save-result__icon {
|
||||
flex-shrink: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sender-editor-save-result__icon--success {
|
||||
background: #d1fae5;
|
||||
}
|
||||
|
||||
.sender-editor-save-result__icon--error {
|
||||
background: #fee2e2;
|
||||
}
|
||||
|
||||
.sender-editor-save-result__title {
|
||||
margin: 0 0 0.4rem;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.sender-editor-save-result__message {
|
||||
margin: 0;
|
||||
color: #6b7280;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.sender-editor-save-result__message--break {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.sender-editor-save-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.sender-editor-save-actions__secondary {
|
||||
border-radius: 6px;
|
||||
padding: 0.5rem 1.25rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sender-editor-save-actions__primary {
|
||||
background: linear-gradient(135deg, #059669, #047857);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 0.5rem 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sender-receiver-chip__body {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
|
||||
Reference in New Issue
Block a user