--- phase: 51-email-html-layout plan: 01 type: execute wave: 1 depends_on: [] files_modified: - database/migrations/20260328_000001_add_html_layout_to_email_mailboxes.sql - src/Modules/Settings/EmailMailboxController.php - src/Modules/Settings/EmailMailboxRepository.php - resources/views/settings/email-mailboxes.php - src/Modules/Email/EmailSendingService.php autonomous: true --- ## Goal Rozbudowa modulu e-mail o HTML layout: header i footer konfigurowane na poziomie skrzynki pocztowej, content z szablonu. Finalna wiadomosc = header + content + footer. Edytor ograniczony do email-safe HTML. ## Purpose Umozliwienie uzytkownikowi stworzenia spojnego brandingu e-mail (naglowek z logo/nazwa firmy, stopka z danymi kontaktowymi) bez powielania tresci w kazdym szablonie. ## Output - Migracja DB: kolumny `header_html`, `footer_html` w `email_mailboxes` - UI skrzynek: dwa edytory Quill (header/footer) z toolbar email-safe - Kompozycja e-mail: header + body + footer w EmailSendingService i preview ## Project Context @.paul/PROJECT.md @.paul/ROADMAP.md @.paul/STATE.md ## Source Files @src/Modules/Settings/EmailMailboxController.php @src/Modules/Settings/EmailMailboxRepository.php @resources/views/settings/email-mailboxes.php @src/Modules/Email/EmailSendingService.php @resources/views/settings/email-templates.php (reference — Quill config) ## Required Skills (from SPECIAL-FLOWS.md) No specialized flows configured as required for this work type. ## AC-1: Kolumny DB header_html i footer_html ```gherkin Given tabela email_mailboxes istnieje When migracja zostanie wykonana Then tabela zawiera kolumny header_html TEXT NULL i footer_html TEXT NULL And istniejace rekordy maja NULL w obu kolumnach (brak breaking change) ``` ## AC-2: Edycja header/footer w formularzu skrzynki ```gherkin Given uzytkownik otwiera formularz edycji skrzynki pocztowej When widzi sekcje "Szablon wiadomosci" pod ustawieniami SMTP Then sa dwa edytory Quill: "Naglowek (header)" i "Stopka (footer)" And toolbar kazdego edytora ogranicza sie do: bold, italic, underline, link, kolor tekstu, kolor tla, wyrownanie, listy, naglowki (h1-h3), obraz (inline base64) And tresc edytorow jest zapisywana do DB przy submit formularza ``` ## AC-3: Kompozycja e-mail header + content + footer ```gherkin Given skrzynka ma ustawiony header_html i footer_html And szablon ma body_html When e-mail jest wysylany lub podgladany (preview) Then tresc wiadomosci = header_html + body_html (resolved) + footer_html And header i footer rowniez przechodza przez variable resolver ``` ## AC-4: E-mail bez header/footer ```gherkin Given skrzynka ma puste (NULL) header_html i/lub footer_html When e-mail jest wysylany Then tresc wiadomosci zawiera tylko body_html (bez pustych sekcji) ``` Task 1: Migracja DB + Repository + Controller database/migrations/20260328_000001_add_html_layout_to_email_mailboxes.sql, src/Modules/Settings/EmailMailboxRepository.php, src/Modules/Settings/EmailMailboxController.php 1. Utworzyc migracje SQL: ```sql ALTER TABLE email_mailboxes ADD COLUMN header_html TEXT NULL AFTER sender_name, ADD COLUMN footer_html TEXT NULL AFTER header_html; ``` 2. EmailMailboxRepository: - `save()`: dodac `header_html` i `footer_html` do INSERT/UPDATE - `findById()`: upewnic sie ze zwraca te kolumny (juz zwraca SELECT * wiec OK) - `listAll()`: bez zmian (nie potrzebuje HTML w liscie) 3. EmailMailboxController: - `save()`: pobrac `header_html` i `footer_html` z POST body - Nie escapowac HTML (to jest tresc edytora WYSIWYG, jak body_html w szablonach) - Przekazac do repozytorium w tablicy save data - Migracja wykonuje sie bez bledow - DESCRIBE email_mailboxes pokazuje header_html i footer_html jako TEXT NULL - Zapis i odczyt skrzynki z header/footer dziala poprawnie AC-1 satisfied: kolumny istnieja i sa zapisywane/odczytywane Task 2: UI edytorow header/footer w formularzu skrzynki resources/views/settings/email-mailboxes.php 1. Po sekcji "Ustawienia SMTP" dodac nowa sekcje "Szablon wiadomosci" z dwoma edytorami Quill 2. Kazdy edytor (header, footer): - Label: "Naglowek (header)" / "Stopka (footer)" - Div z id `js-header-editor` / `js-footer-editor` (kontener Quill) - Hidden input `header_html` / `footer_html` (sync przy submit) - Podpowiedz: "Opcjonalnie. Bedzie dolaczany do kazdego e-maila wysylanego z tej skrzynki." 3. Toolbar edytora — TYLKO email-safe opcje: ```javascript [ [{ header: [1, 2, 3, false] }], ['bold', 'italic', 'underline'], [{ color: [] }, { background: [] }], [{ align: [] }], [{ list: 'ordered' }, { list: 'bullet' }], ['link', 'image'], ['clean'] ] ``` - Image: Quill domyslnie wstawia jako base64 inline — to jest email-safe - Brak: strike, blockquote, code-block, video, indent (slabo obslugiwane w klientach pocztowych) 4. Zaladowac Quill CSS/JS z CDN (ten sam co w email-templates: 2.0.3) 5. Na submit formularza: sync innerHTML z edytorow do hidden inputs 6. Przy edycji istniejacego rekordu: zaladowac HTML do edytorow przez `quill.root.innerHTML = ...` 7. Edytory powinny miec mniejsza domyslna wysokosc niz edytor szablonu (np. min-height: 80px) - Otworz /settings/email-mailboxes — formularz pokazuje dwa edytory - Wpisz tresc w header/footer, zapisz — dane sa w DB - Edytuj skrzynke — header/footer sa zaladowane do edytorow - Toolbar nie zawiera opcji niebezpiecznych dla e-mail (video, code-block) AC-2 satisfied: edytory sa dostepne, ograniczone do email-safe, i zapisuja dane Task 3: Kompozycja e-mail w EmailSendingService src/Modules/Email/EmailSendingService.php 1. W metodzie `send()` po rozwiazaniu zmiennych w body: - Pobrac header_html i footer_html z resolved mailbox - Przepuscic je przez variableResolver.resolve() (aby zmienne dzialaly tez w header/footer) - Zlozyc finalBody: header_html + resolvedBody + footer_html - Jezeli header_html lub footer_html sa NULL/puste — pominac (bez pustych divow) - Uzyc finalBody zamiast resolvedBody w sendViaSMTP i logEmail 2. W metodzie `preview()`: - Analogicznie: pobrac mailbox dla szablonu, zlozyc header + body + footer - Aby preview dzialal, potrzebujemy mailbox — uzyc resolveMailbox() (juz istnieje) - Jezeli mailbox nie znaleziony — pokazac sam body (bez header/footer) 3. Metoda `resolveMailbox()` jest private — juz zwraca pelne dane z findById() wlacznie z header_html/footer_html 4. Nowa prywatna metoda `composeBody(string $resolvedBody, ?array $mailbox, array $variableMap): string`: - Wydzielic logike kompozycji do reusable metody - Uzyc w send() i preview() - Wyslij e-mail ze skrzynka z ustawionym header/footer — e-mail zawiera header + body + footer - Wyslij e-mail ze skrzynka BEZ header/footer — e-mail zawiera tylko body - Preview pokazuje zlozony wynik header + body + footer - Zmienne w header/footer sa rozwiazywane (np. {{firma.nazwa}}) AC-3 i AC-4 satisfied: kompozycja dziala z i bez header/footer ## DO NOT CHANGE - resources/views/settings/email-templates.php (edytor szablonow — bez zmian) - src/Modules/Email/VariableResolver.php (resolver zmiennych — bez zmian) - src/Modules/Email/AttachmentGenerator.php - database/migrations/ (istniejace migracje) - Struktura tabeli email_templates (body_html pozostaje jak jest) ## SCOPE LIMITS - Brak edytora MJML / dedykowanego email buildera — Quill z ograniczonym toolbar wystarcza - Brak podgladu header/footer w formularzu skrzynki (preview jest w szablonach) - Brak importu gotowych szablonow HTML - Zmienne w header/footer dzialaja, ale panel zmiennych NIE jest dodawany do formularza skrzynki (header/footer to zwykle statyczny branding) Before declaring plan complete: - [ ] Migracja wykonana, kolumny widoczne w DESCRIBE - [ ] Formularz skrzynki: dwa edytory Quill z email-safe toolbar - [ ] Zapis i odczyt header/footer dziala - [ ] E-mail wysylany ze skrzynka z header/footer zawiera zlozony layout - [ ] E-mail wysylany ze skrzynka BEZ header/footer zawiera tylko body - [ ] Preview pokazuje zlozony wynik - [ ] Zmienne w header/footer sa rozwiazywane - [ ] Brak bledow PHP/JS w konsoli - Wszystkie 3 taski zakonczone - Wszystkie 4 acceptance criteria spelnione - Wszystkie verification checks przeszly - Brak regresji w istniejacym wysylaniu e-mail After completion, create `.paul/phases/51-email-html-layout/51-01-SUMMARY.md`