Rozszerzenie powiadomień o kolejne typy zdarzeń, z symetrycznymi togglami
e-mail i push w /konto/prywatnosc.
Migracje 103 + 104 — 6 nowych kolumn preferencji e-mail + NordaEvent.reminder_24h_sent_at.
Triggery:
- Forum odpowiedź → push do autora wątku (notify_push_forum_reply)
- Forum cytat (> **Imię** napisał(a):) → push + email do cytowanego
(notify_push/email_forum_quote)
- Admin publikuje aktualność → broadcast push (ON) + email (OFF)
do aktywnych członków (notify_push/email_announcements)
- Board: utworzenie / publikacja programu / publikacja protokołu
→ broadcast push + opt-in email (notify_push/email_board_meetings)
- Nowe wydarzenie w kalendarzu → broadcast push + email (oba ON)
(notify_push/email_event_invites)
- Cron scripts/event_reminders_cron.py co godzinę — wydarzenia za 23-25h,
dla zapisanych (EventAttendee.status != 'declined') push + email,
znacznik NordaEvent.reminder_24h_sent_at żeby nie dublować.
Email defaults dobrane, by nie zalać inbox: broadcast OFF (announcements,
board, forum_reply), personalne/actionable ON (forum_quote, event_invites,
event_reminders).
Wszystkie nowe e-maile mają jednym-kliknięciem unsubscribe (RFC 8058
+ link w stopce) — unsubscribe_tokens.py rozszerzony o nowe typy.
Cron entry do dodania na prod (osobny krok, bo to edycja crontaba):
0 * * * * cd /var/www/nordabiznes && venv/bin/python3 scripts/event_reminders_cron.py
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Link z listy tematów (forum/index, dashboard, strona główna) prowadzi
teraz do kotwicy #reply-latest — wstawionej w topic.html tuż przed
ostatnim widocznym replyem. scroll-margin-top:90px żeby sticky nav
nie zasłaniał. Brak zmian w backendzie (anchor HTML, zero round-trip).
Admin panele (forum_reports, forum_analytics, forum.html, forum_deleted)
zostawiam — tam moderator ogląda temat od góry.
Topic bez replies (sam początkowy post) zachowuje się bez zmian
(anchor nie istnieje → scroll na górę).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Każdy e-mail powiadomieniowy ma teraz:
(1) link w stopce "Wyłącz ten typ powiadomień jednym kliknięciem"
(2) nagłówki List-Unsubscribe + List-Unsubscribe-Post dla klientów
pocztowych (Gmail/Apple Mail pokażą natywny przycisk Unsubscribe)
Implementacja:
- utils/unsubscribe_tokens.py: signed token (itsdangerous, SECRET_KEY)
niosący user_id + notification_type, bez wygasania
- blueprints/unsubscribe: GET /unsubscribe?t=TOKEN → strona potwierdzenia,
POST /unsubscribe → faktyczne wyłączenie flagi notify_email_<type>
- email_service.send_email() dostał parametr notification_type. Jeśli
przekazany razem z user_id, footer + headery są doklejane
- Aktualizowane wywołania: message_notification (messages),
classified_question/answer (B2B Q&A), classified_expiry (skrypt cron)
Prefetch safety: GET pokazuje stronę z przyciskiem "Tak, wyłącz",
wyłączenie następuje po POST. RFC 8058 One-Click (POST bez formularza
z Content-Type application/x-www-form-urlencoded + body
"List-Unsubscribe=One-Click") obsługuje klientów pocztowych.
D.2/D.3 dorzucą kolejne notification_type (forum, broadcast, events).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Symetria z push — panel /konto/prywatnosc rozszerzony o 3 dodatkowe
toggle w karcie "Powiadomienia e-mail":
- Pytanie pod moim ogłoszeniem B2B (notify_email_classified_question)
- Odpowiedź pod moim pytaniem B2B (notify_email_classified_answer)
- Ogłoszenie wygasa za 3 dni (notify_email_classified_expiry)
Migracja 102 dodaje kolumny (default TRUE — nie zmienia zachowania
istniejących userów). Endpointy ask_question / answer_question teraz
czytają dedykowaną flagę zamiast notify_email_messages (która zostaje
tylko dla wiadomości prywatnych). Skrypt classified_expiry_notifier.py
pomija userów z wyłączonym notify_email_classified_expiry.
W kolejnych sub-fazach D.2/D.3 symetrycznie dojdą triggery e-mail +
toggle dla forum/broadcast/wydarzeń — z defaults dobranymi tak, by
nie zalać inbox użytkowników (broadcast OFF, personalne ON).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Migracja 101 dodaje 8 nowych kolumn notify_push_* na users (wszystkie
default TRUE). Panel preferencji rozszerzony o kartę "Powiadomienia
push (na urządzeniu)" z 3 podsekcjami (interakcje dot. mnie, aktualności
Izby, wydarzenia) — 9 przełączników. "Nowa wiadomość prywatna" świadomie
jest w obu kartach (e-mail + push) — userzy mogą niezależnie wybrać
oba kanały.
Triggery B2B:
- zainteresowanie ogłoszeniem (ClassifiedInterest) → push do autora
z notify_push_classified_interest
- pytanie do ogłoszenia (ClassifiedQuestion) → push do autora z
notify_push_classified_question
Fazy D.2 (forum + broadcast) i D.3 (wydarzenia + cron) w kolejnych PR.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Odróżnia push od portalowych powiadomień w navbarze. Po pierwszym
udanym włączeniu pokazuje komunikat że subskrypcja działa tylko na
tym urządzeniu i zachęca do kliknięcia również na innych telefonach
i komputerach. Tooltipy zaktualizowane pod kątem "na tym urządzeniu".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
B2B ogłoszenia mogły zostać stworzone 3x (user 81 Bormax 14.04.2026
w ciągu 2 sekund) — brak dedup window server-side i disable submit
button. Rozszerzam zabezpieczenie także na announcements i board
meeting form.
- classifieds POST /nowe: odrzuć duplikat z ostatnich 60s (ten sam
author+company+title) → redirect do istniejącego z flash info
- classifieds new.html: disable submitBtn + "Wysyłanie..." po
walidacji; ponowne kliknięcie blokowane event.preventDefault
- announcements_form.html + board/meeting_form.html: jednolity
handler disable wszystkich button[type="submit"] po pierwszym
submit
Forum topic/reply już miały analogiczne zabezpieczenie (bez zmian).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Google Maps URLs can be 800+ chars of tracking data that poisoned the
forum UI. Extract the place name from /maps/place/NAME/ (or fall back
to coordinates) and render as '📍 Name'. Full URL remains in the href.
Two secondary fixes:
- Edit/quote modals were reading .innerText of the rendered reply,
which baked the current render (including any stale/broken HTML from
older bad renders) back into the textarea. Switched to emitting the
raw DB content via {{ content|tojson }} so what you edit is what you
wrote.
- @mention regex was matching '@54.1234' inside Maps URLs and similar.
Tightened to require a letter start and non-slash/non-word lookbehind
so coords and email-style strings pass through untouched.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The :invalid CSS without scoping was making the entire form-container
draw a red border (the form is :invalid as long as any inner field is).
Removed it. Replaced with positive feedback: a green ✓ appears next to
the label of each required field as soon as it is filled. Tracks title,
category, listing_type radios and Quill description (new.html) plus
title and description (edit.html). Initial pass at load sets the check
on values restored after a POST validation error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
document.querySelector('form') was matching the company switcher form
in the navbar, not the classifieds form. Quill content was therefore
never synced into the hidden description textarea, server saw it empty
and rejected the submit with a misleading 'fill all fields' error.
Added id='classifiedForm' to both new.html and edit.html and switched
selectors to getElementById.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously when server validation failed (e.g. missing required field),
the whole form re-rendered with all values cleared — user had to retype
everything. Also Quill empty-content showed an alert dialog.
Now:
- Server-side: form_data + missing_fields passed to template; values
re-populate inputs, missing fields get .field-error class (red border)
- Quill empty: red border on the editor container instead of alert,
cleared as soon as user starts typing
- Other required fields (radio, select, title): same .field-error
treatment plus :invalid CSS for live HTML5 feedback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two user-reported regressions:
1. B2B classifieds "Dodaj ogłoszenie" button silently no-op'd. Hidden
Quill description textarea had `required` attribute — browser blocked
submit but cannot show validation UI on display:none fields. Removed
`required`, added empty-content guard in submit handler with explicit
alert.
2. Forum auto-linker truncated Google Maps URLs containing two
underscores (e.g. forestry_office...g_ep=). Italic regex `_text_`
matched across the URL, splitting it with <em> tags. Italic/bold
underscore forms now require non-word boundary so URLs and identifiers
like my_var_name pass through untouched.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Meta deprecates page_impressions, post_impressions, page_video_views et al.
on 2026-06-30. Replaced by *_media_view family. Both old and new metrics
are requested during the transition window so historical data and fresh
data coexist without UI gaps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- parse_mentions_and_notify now sends email to mentioned user
(separate from forum subscription emails — fires on every mention)
- parse_forum_markdown accepts current_user_name; mentions matching
the viewer get extra .forum-mention-self class
- topic.html passes current_user.name to filter; .forum-mention-self
styled with amber background + bold + ring
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Backend: reject identical title+content from same author within 60s
(mirrors existing protection on forum_reply)
- Frontend: disable submit button + 'Wysyłanie…' label on first click
Daniel Kochański accidentally created 7 identical 'Local content w praktyce'
topics within 5 seconds. Soft-deleted IDs 25-30 on prod, kept 24.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Admin fees yearly view now shows all active companies (including child brands)
- Child brand rows are indented with striped month cells and "firma córka" badge
- Parent companies show expandable brand list, Stawka column with 200/300 zł logic
- Expected fee per month computed from number of active child brands
- Rate change month shown when brand joins mid-year (e.g. "I-III: 200 zł / od IV: 300 zł")
- Sorting groups children directly under their parent
- Reminder logic skipped for child companies
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Shows expected fee per company (200 zł for 1 brand, 300 zł for 2+)
- Child companies shown with striped "nie dotyczy" tiles
- Rate change month displayed (e.g., "I-III: 200 zł, od IV: 300 zł")
- Expandable brand list under parent company name
- Children grouped after their parent in the table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add sort keys and data-sort-value attributes to 'Upr. firmowe' and 'Rola' columns
- Add filter tabs for MANAGER, OFFICE_MANAGER, company-role NONE and MANAGER
- Add data-company-role attribute to user rows for JS filtering
- Grant OFFICE_MANAGER access to admin_users, assign-company, reset-password, change-role, get-roles endpoints
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All "Powrót do wiadomości" links in compose, view, sent, and group_compose
templates now point to messages.conversations_page instead of legacy
messages_inbox.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- "Profil firmy" button becomes a dropdown listing all companies
- Subtitle under name shows all companies separated by dots
- Single-company users see no changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace company_id from current_user with active company from session in
the colleagues API endpoint, and autofill guest org from active_company.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Users with multiple companies now see a dropdown to choose which company
a B2B classified ad is posted for. Single-company users get a hidden field.
Server-side validates the selected company_id against user's actual memberships.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace single "Moja firma" link with a switchable company list for users
with multiple companies, using POST forms with CSRF and active-state highlighting.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace textarea with Quill editor in new/edit classified forms
- Sanitize HTML with sanitize_html() on save (XSS prevention)
- Render HTML in classified detail view, strip tags in list view
- New script: classified_expiry_notifier.py sends email 3 days before
expiry with link to extend. Run daily via cron at 8:00.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added to GeoIP tab:
- Last 20 blocked requests with IP, country, path, timestamp
- Top 10 most targeted URL paths with hit counts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reply seen-by section was hardcoded to show initials only.
Added avatar_path check matching topic readers pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added CSS rule for .reader-avatar img with absolute positioning
to properly display avatar photos inside the 28px circles.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Portal is production - no more test topics. Changed topic #1 category
from 'test' to 'announcement'. Removed toggle-test UI from forum list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Expired classifieds show 'Wygasło' badge on list and detail view
- Closed classifieds show 'Zamknięte' badge on list
- Author can extend by 30 days with one click
- Homepage 'Nowe na portalu' excludes expired classifieds
- List shows all classifieds, active first
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use raw SQL UPDATE for views_count to bypass SQLAlchemy onupdate.
Restore updated_at display in homepage cards - now accurate.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
updated_at was being triggered by views_count increment on every page
view, making dates misleading. Reverted to created_at for display and sort.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Forum cards now show date of latest reply (not topic creation).
B2B cards show updated_at (not created_at), sorted by most recent activity.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace 'Najnowszy wpis na forum' (duplicate with Nowe na portalu)
with 'Nowi użytkownicy portalu' showing 4 latest registered users
with avatars, names, dates and company names.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows 2 latest forum topics + 2 latest B2B classifieds in a 4-column
grid between events and NordaGPT banner. Responsive 2-col on mobile.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>