Files
paliad/frontend/src/admin-email-templates-edit.tsx
m ba2408eb51 feat(paliadin/inline-widget): t-paliad-161 Slice C — floating button + slide-out drawer
The inline Paliadin chat surface — reachable from every authenticated
page, replacing the standalone /paliadin route as the primary entry
point. The standalone page survives as the dedicated full-screen mode
(the drawer's "↗ fullscreen" action links to it).

Components:

- frontend/src/components/PaliadinWidget.tsx — emits the floating
  trigger button (bottom-right, lime , owner-revealed by JS), a
  scrim, and the right-edge slide-out drawer with header (reset /
  fullscreen / close), context chip, message stream, empty-state
  starter list, and textarea+send form. Loads /assets/paliadin-widget.js.

- frontend/src/client/paliadin-widget.ts — runtime. /api/me probe
  reveals the trigger when caller matches PaliadinOwnerEmail (with
  optional is_paliadin_owner flag fast-path); Cmd+J / Ctrl+J shortcut
  toggles open/close (Cmd+K stays reserved for global search per
  client/search.ts). Uses computePaliadinContext() (Slice B) per send
  so route + entity + selection flow into every turn. SSE consumer
  writes assistant bubbles; localStorage persists per-session history.

- frontend/src/client/paliadin-starters.ts — per-route starter prompt
  registry. 14 routes covered (dashboard, projects.*, deadlines.*,
  appointments.*, agenda, events, inbox, tools.*, glossary, courts) +
  a _default fallback. Bilingual (DE/EN); prompts ending in `: ` seed
  the textarea for the user to finish; fully-formed prompts auto-send.

- 39 authenticated TSX pages get a `<PaliadinWidget />` element after
  `<Footer />` via a mechanical pass. paliadin.tsx (the standalone)
  is intentionally excluded — its dedicated UI is the widget's
  fullscreen escape hatch, not a place to overlay another widget.

- frontend/build.ts registers the new bundle.
- frontend/src/styles/global.css gains ~280 lines of widget CSS
  (trigger / scrim / drawer / header / context-chip / messages /
   bubbles / starters / form / send-btn) using only existing tokens.
   Mobile (≤640px): drawer goes full-screen; trigger lifts above
   bottom-nav slots.
- 11 new i18n keys × 2 langs = 22 entries under paliadin.widget.*.

Visibility predicate (paliadin-context.shouldSendContext) hides the
widget on /paliadin, /login, /onboarding. Owner-only gate stays on
PaliadinOwnerEmail.

Build clean: i18n 1955 → 1966 keys, IIFE-wrapped 218KB bundle, go test
green.

Refs: docs/design-paliadin-inline-2026-05-08.md §3, §5.
2026-05-08 19:54:18 +02:00

120 lines
6.1 KiB
TypeScript

import { h } from "./jsx";
import { Sidebar } from "./components/Sidebar";
import { PaliadinWidget } from "./components/PaliadinWidget";
import { BottomNav } from "./components/BottomNav";
import { Footer } from "./components/Footer";
import { PWAHead } from "./components/PWAHead";
// /admin/email-templates/{key}?lang=de — full editor. The shell holds the
// chrome and the empty form/preview/variable wells. The client bundle reads
// the key from location.pathname and the lang from location.search, fetches
// the active row + variables + version log in parallel, and populates.
export function renderAdminEmailTemplatesEdit(): string {
return "<!DOCTYPE html>" + (
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta name="theme-color" content="#BFF355" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<PWAHead />
<title data-i18n="admin.email_templates.editor.title">Email-Template bearbeiten &mdash; Paliad</title>
<link rel="stylesheet" href="/assets/global.css" />
</head>
<body className="has-sidebar">
<Sidebar currentPath="/admin/email-templates" />
<BottomNav currentPath="/admin/email-templates" />
<main>
<section className="tool-page">
<div className="container admin-et-edit-container">
<div className="tool-header">
<div>
<a href="/admin/email-templates" className="admin-et-back" data-i18n="admin.email_templates.back">
&larr; Zurück zur Liste
</a>
<h1 id="admin-et-title" data-i18n="admin.email_templates.editor.heading">Email-Template bearbeiten</h1>
<p id="admin-et-subtitle" className="tool-subtitle" />
</div>
<div className="admin-et-lang-toggle" id="admin-et-lang-toggle" role="tablist" aria-label="Language">
<button type="button" className="admin-et-lang-btn" data-lang="de" aria-pressed="true">DE</button>
<button type="button" className="admin-et-lang-btn" data-lang="en" aria-pressed="false">EN</button>
</div>
</div>
<div id="admin-et-feedback" className="form-msg" style="display:none" />
<div className="admin-et-editor">
<div className="admin-et-editor-form">
<div className="form-field" id="admin-et-subject-wrap">
<label htmlFor="admin-et-subject" data-i18n="admin.email_templates.editor.subject">Betreff</label>
<input type="text" id="admin-et-subject" className="admin-et-subject-input" autocomplete="off" />
</div>
<div className="form-field">
<label htmlFor="admin-et-body" data-i18n="admin.email_templates.editor.body">HTML-Body</label>
<textarea id="admin-et-body" className="admin-et-body-input" rows={24} spellcheck={false} />
</div>
<div className="form-field">
<label htmlFor="admin-et-note" data-i18n="admin.email_templates.editor.note_optional">Notiz (optional)</label>
<input type="text" id="admin-et-note" className="admin-et-note-input" autocomplete="off"
placeholder="z.B. Korrektur nach Anwalts-Feedback"
data-i18n-placeholder="admin.email_templates.editor.note_placeholder" />
</div>
<details className="admin-et-variables">
<summary data-i18n="admin.email_templates.editor.variables">Verfügbare Variablen</summary>
<div id="admin-et-variables-list" className="admin-et-variables-list" />
</details>
<div className="form-actions admin-et-actions">
<button type="button" id="admin-et-save" className="btn-primary" disabled
data-i18n="admin.email_templates.editor.save">Speichern</button>
<button type="button" id="admin-et-reset" className="btn-secondary"
data-i18n="admin.email_templates.editor.reset">Auf Standard zur&uuml;cksetzen</button>
</div>
</div>
<div className="admin-et-editor-preview">
<div className="admin-et-preview-header">
<h2 data-i18n="admin.email_templates.editor.preview">Vorschau</h2>
<div className="admin-et-preview-actions">
<select id="admin-et-slot" className="admin-et-slot-select" style="display:none">
<option value="morning" data-i18n="admin.email_templates.editor.slot.morning">Morgen-Slot</option>
<option value="evening" data-i18n="admin.email_templates.editor.slot.evening">Abend-Slot</option>
</select>
<button type="button" id="admin-et-preview-refresh" className="btn-tertiary"
data-i18n="admin.email_templates.editor.preview_refresh">Vorschau aktualisieren</button>
</div>
</div>
<div className="admin-et-preview-subject" id="admin-et-preview-subject" />
<iframe
id="admin-et-preview-frame"
className="admin-et-preview-frame"
sandbox="allow-same-origin"
title="Email preview"
/>
<details className="admin-et-versions">
<summary data-i18n="admin.email_templates.editor.versions">Versionen</summary>
<ul id="admin-et-versions-list" className="admin-et-versions-list" />
</details>
</div>
</div>
</div>
</section>
</main>
<Footer />
<PaliadinWidget />
<script src="/assets/admin-email-templates-edit.js"></script>
</body>
</html>
);
}