Changelog
Historie změn pro testery. Tato stránka bude odstraněna před produkčním spuštěním.
7. července 2026
- Integrační testovací sada (Fáze 1) — automatizované testy před manuálním testováním
- • **A — Bezpečnost/RLS:** anon-execute (regrese anon-EXECUTE fixu), anon-read (C3/FL-H1 anonymizace), C2 eskalace (role/status pin, cross-user notif), AAL2 admin (TOTP přes `otplib` — admin bez MFA nemá práva).
- • **B — Lifecycle:** create_match FL-K1 end-to-end + reveal, guardy (ownership/rejected/neaktivní/dvojí přijetí), FOMO reveal, M-6 cancel→reopen, FL-I sync sold/revert, lead cancel FL-M8.
- • **C — API routes:** extend-lead (Bearer, ownership, kotva), confirm-contract (→contracted + protistrana), lead-photos G-8 autorizační matice (seller/broker-match/admin-AAL2 → vše; broker bez matche → jen interiér; neaktivní/AAL1 → 403). report-sale + confirm-sale-seller guarded (notifikují reálné adminy → běží jen bez reálného admina).
- • **D — CRON:** expire-leads auth-gate (401) + efektový test (fáze B expirace, guarded na reálná data, `SUPABASE_CRON_KEY`).
6. července 2026
- Bezpečnostní oprava — anon EXECUTE na citlivých SECURITY DEFINER funkcích (automated-testing sweep)
- • **#1 HIGH — `get_user_email_by_id`**: anon vytáhl **e-mail libovolného uživatele podle UUID** (GDPR únik). Ověřeno reálným HTTP: `POST /rest/v1/rpc/get_user_email_by_id` s anon klíčem → před opravou vrátilo e-mail, po opravě `401 / 42501 permission denied`. Fix: `REVOKE ... FROM public, anon`; ponechán `authenticated` (admin volá client-side, guard `is_admin`) + `service_role`.
- • **#2 MEDIUM — `create_notification`**: anon injektoval in-app notifikaci komukoli (phishing `title/body/href`). Ověřeno `SET ROLE anon` (rollback) → vrácen UUID. Fix stejný (guard cross-user chrání `authenticated`).
- • **#3 MEDIUM — `get_bidding_broker_ids` / `get_broker_bid_lead_ids` / `get_matched_seller_ids` / `get_seller_lead_ids`**: bezguardové orphaned DEFINER helpery → enumerace/deanonymizace makléřů/leadů/matchů podle UUID (obchází model anonymního kruhu). Nevolají se z app kódu, RLS policies ani views (ověřeno) → `REVOKE ... FROM public, anon, authenticated`, zůstává jen `service_role`.
- FL-L/FL-I závěrečná dávka — flow audit KOMPLETNĚ UZAVŘEN
- • **„Moje nabídky" s kontextem i pro pending/rejected bidy**: nové snapshot sloupce na `bids` (`lead_location_area_snapshot`, `lead_property_type_snapshot`, `lead_params_snapshot {size_m2, rooms}`) plněné BEFORE INSERT triggerem `snapshot_bid_lead_context` (SECURITY DEFINER — čte VŽDY z leads, klientský podvrh přepíše) + backfill. Dřív RLS (`leads_select` jen matchnuté) + view (jen aktivní neobsazené) → „Poptávka nedostupná" přesně u pending bidů na plných leadech. `exact_address` dál jen u accepted (base join přes match). Jen anonymizovaná pole, která broker viděl při podání — žádný nový únik; GDPR-imunní (vzor `matches.*_snapshot`).
- • **`price_label` už neodchází brokerovi v payloadu**: `use-broker-lead-detail` + `broker/bids` z `select("*")` na explicitní column listy (cenový semafor je seller-only; UI ho nerenderovalo, ale network response ano).
- • **„Makléř #N" čísluje podle příchodu**: bidy řazeny ASC (dřív DESC → #1 = nejnovější; kolečka slotů implikují pořadí příchodu).
- • **Admin matches — mapa vs. handlery sjednoceny**: `sold→watching` doplněn do VALID_TRANSITIONS (revert plomba false-positive, zrcadlí PŘESNĚ nulování z detail handleru); inline `contracted→watching` nuluje contract timestampy (jinak duplicate guard 400 při re-potvrzení); **`invoiced→cancelled` ODEBRÁN** (rozhodnutí — po faktuře jen →paid, cancel nechával evidovanou fakturu a neotevřel lead) vč. skrytí tlačítka Zrušit na detailu; **inline `cancelled` odfiltrován ze selectu** (cancel jen přes detail — dialog s důvodem + `watchdog_active=false`); lokální sync stavu přepisuje pole VČETNĚ null (dřívější `??` null tiše spolkl).
- • **submit-rating deep-link**: fallback `rating_id=match_id` odstraněn (rozbíjel auto-expand na /admin/ratings).
- • **Zvoneček a /notifications sdílí state** (rozhodnutí: bez Realtime): nový `NotificationsProvider` (jeden 60s polling pro celou app + refetch při fokusu okna s 5s throttle); přečtení na stránce se na badge projeví okamžitě; `useNotifications` je nyní context consumer.
- • **Fáze B check-in = plný dual-write** (rozhodnutí): strany dostávají e-mail + in-app (`contract_reminder`, gate `cooperation/inapp` — vzor fáze A), admini in-app + NOVĚ e-mail (šablona `contract_unsigned`, bez pref gate).
- • **NOTIFICATION_CONFIG synchronizován s realitou**: 5 mrtvých klíčů pryč (`match_seller/broker`, `payment_reminder`, `checkin_request/reminder`), ~12 reálných typů doplněno, mrtvé pole `icon` odstraněno; drift guard test.
- • OAuth-only uživatel: karta „Změna hesla" nahrazena info boxem (dialog vždy selhal na ověření aktuálního hesla).
- • `verify-email` rozlišuje výsledky: expirovaný token → `expired_token` (hláška byla dřív nedosažitelná), už ověřený → friendly `email-verified?already=1`; copy `invalid_token` kryje i použitý odkaz (úspěch token nuluje).
- • Chybové query params se konečně renderují: login zobrazí `?reason=mfa_check_failed` i `?error=oauth_failed/missing_code` (Suspense refaktor), homepage `/?deleted=1` → toast potvrzení smazání účtu (`DeletedAccountNotice`).
- • `/auth/suspended` SMAZÁNA (0 referencí — read-only přístup s bannerem na dashboardu je záměr); `/auth/pending-approval` už nerenderuje kartu nepřihlášenému (redirect login/profile-error/loader); `termsOutdated` mrtvé API odstraněno z useAuthGuard; `requireMfa` latentní dead-end pro non-admin opraven (mfaChecked defensivně); text na verify stránce pravdivý (mažou se jen účty bez jediného ověření).
- • **Resubmit čistí review metadata** (H-D trigger **v5**): `rejected→pending_review` nuluje `reviewed_by/reviewed_at` (dřív držely zamítajícího admina a čas rejectu do dalšího schválení).
- • **Cleanup rejected leadů** (rozhodnutí: 30 dní): nová fáze C v expire-leads (`cleanup-rejected.ts`) — rejected lead 30+ dní od zamítnutí (guard i na `updated_at` — aktivně editovaný draft se nemaže) → smazání fotek přes Storage API (vzor delete-account, fotky PŘED řádkem) + DELETE řádku. Rejected lead nikdy nebyl aktivní → bez bidů/matchů.
- • `cleanup_unverified_accounts` **v3**: odstraněn mrtvý soft-delete UPDATE blok (hard DELETE + FK CASCADE ho činil no-op), harden search_path; chování beze změny.
- • Docs drift: DATABASE.md notifications RLS opraveno (žádná `insert_authenticated` policy neexistuje).
5. července 2026
- FL-I dávka 3: lead sold sync, připomínka sale_reported, duplicitní lead, cancel pending_review
- • **`leads.status='sold'` má konečně zapisovatele** (rozhodnutí: zapisovat): nový trigger `sync_lead_status_from_match` — vstup matche do `sold/invoiced/paid` přepne lead na `sold` (guard `status='matched'`), revert zpět na `watching/contracted/sale_reported` vrátí lead na `matched`. Pokrývá všechny zapisovatele match statusu (admin UI detail, inline select, budoucí routes) jedním místem; cancel větev dál řeší M-6. Dřív lead zůstával `matched` navždy a admin přehledy neuměly odlišit prodané.
- • **Visící `sale_reported` se připomíná** (rozhodnutí: opakovaný CRON nag): nová fáze E v checkin-send — report starší 7 dní bez ověření → in-app připomínka aktivním adminům s počtem dní, opakuje se à 7 dní (nový sloupec `matches.sale_report_reminder_sent_at`, at-most-once claim vzor fází A–D). Dřív byl admin notifikován jen jednou při reportu a watchdog stav přeskakuje.
- • **Duplicitní poptávka blokována i při běžící zakázce** (rozhodnutí: blokovat): kontrola duplicit v new-lead rozšířena o stav `matched` (parcela + jednotka + typ) — druhá poptávka na tutéž nemovitost během zakázky obcházela provizi.
- • **Seller může stáhnout neschválenou poptávku** (rozhodnutí: povolit): H-D trigger **v4** whitelistuje `pending_review→cancelled`; tlačítko Zrušit + dialog v detailu i pro pending_review; `handleCancelLead` přijímá oba stavy. Přibalený FL-L fix: tlačítko Prodloužit se renderuje jen u `active`/`expired` (dřív disabled i u pending_review/rejected) + odstraněna mrtvá toast větev.
- • **Follow-up — seller UI + RLS pro nový stav `sold`** (vlastní kontrola dopadu): seller detail/list byly klíčované na `status === "matched"` → po přepnutí na sold by zmizel kontakt makléře, reveal bidů i lead ze záložky Spárováno. Opraveno na `["matched","sold"]` (use-lead-detail, section-bids, section-lead-detail, seznam); **migrace `20260705032530`**: bids SELECT policy reveal větev `status IN ('matched','sold')` (ověřeno na živé DB: active 1/5 → 0 bidů, sold → bid viditelný).
- • 282 testů, tsc + eslint + build ✓.
- FL-L dávka 2: spolehlivost notifikací, audit přesnost, race conditions
- • **Prázdné jméno → tichá ztráta e-mailu (v45):** EF vyžadovala `to_name` (400) — SQL triggery skládají `TRIM(first||' '||last)`, profil bez jmen = e-mail se tiše ztratil (`net.http_post` fire-and-forget). → `to_name` volitelné, prázdné/chybějící jméno = neutrální oslovení „uživateli" (šablony renderují „Dobrý den, X!" — prázdný string by dal „Dobrý den, !").
- • **checkin-send v14 — at-most-once:** všechny 4 fáze (A/B/C/D) zapisovaly flag až PO odeslání → pád mezi sendem a flagem = duplicitní e-maily při dalším CRON běhu. → flag se zapisuje PŘED odesláním atomickým claimem (`.is(null)`, u fáze D optimistic lock na předchozí timestamp, + `.select()`) — kryje i souběžné běhy. Trade-off (pád po claimu = e-mail chybí) dokumentován: fázi A dožene B, C dožene D; duplicitní urgence jsou horší než chybějící.
- • **expire-leads v9 — fáze A přestavěna:** `if (!emailData) continue;` přeskočil i in-app notifikaci a flag → seller bez e-mailu dostával warning recyklovaný každý den; flag po sendu = double-send okno. → atomický claim PŘED odesláním (flag se zapíše vždy — i bez e-mailu / vypnutých preferencí), e-mail lookup až po claimu, in-app se pošle i bez e-mailu. Inline šablony: HTML escaping user-controlled hodnot (jméno, lokalita — vzor M-2) + vokativ oslovení (lokální kopie `vocative.ts`).
- • **Filtr aktivních adminů sjednocen:** `report-sale`, `confirm-sale-seller`, `confirm-contract`, `email-action` (2×) a checkin-send fáze B nefiltrovaly `status='active'` → suspendovaný admin dál dostával notifikace (watchdog-check/submit-rating/support-ticket filtr měly). → `.eq("status","active")` všude.
- • **Audit přesnost (právní důkaz):** `report-sale` hardcodoval `changes old:"watching"` i pro match ve stavu `contracted` → nově loguje skutečný `match.status`. (Druhá půlka nálezu — `confirm-sale-seller` prázdné `changes` — už byla vyřešena přepisem FL-H4.)
- • **handleCancelLead race:** UPDATE s `.eq("status","active")` při mezitím změněném stavu (matched/expired) vrátil 0 řádků bez chyby → zrušení vypadalo jako úspěch. → `.select()` + kontrola počtu řádků + hláška „stav se mezitím změnil".
- • **Admin approve/reject optimistic lock:** zastaralá záložka mohla vrátit k přepracování už auto-schválený lead (CRON lock měl, admin UI ne). → `.eq("status", lead.status)` + `.select()` kontrola + toast; notifikace sellerovi jen při skutečné změně.
- • Testy: mocky admin dotazů rozšířeny o `.eq(status)` řetěz. **282 testů, 25 suites**, tsc + eslint + build ✓.
- AAL2 vynucení pro adminy — is_admin() + admin API routes
- • **Migrace `20260705020900`**: `is_admin()` nově `role = 'admin' AND auth.jwt()->>'aal' = 'aal2'` (chybějící JWT/claim → false, fail-closed). Jeden chokepoint — pokrývá všech 27 policies + 7 SECURITY DEFINER funkcí/triggerů naráz; předem ověřeno, že VŠICHNI volající předávají `auth.uid()` (aktuální session), nikdy cílového uživatele. Nedotčeno: service_role/CRON (`auth.uid() IS NULL` bypassy PŘED voláním is_admin), seller/broker, admin UI (po MFA verify má aal2 — useAuthGuard fail-closed), čtení vlastního profilu před MFA (větev `id = auth.uid()`).
- • **Admin API routes**: nový helper `lib/server/jwt-aal.ts` (`getTokenAal`/`isAal2` — dekóduje payload UŽ OVĚŘENÉHO tokenu, podpis neověřuje) nasazen v `send-notification`, `support-ticket/reply`, `test-watchdog` (token ze `getSession` — cookie-based klient) a `lead-photos` (admin větev autorizační matice jen s AAL2, jinak propad do 403).
- • **Ověřeno na živé DB** (kandidát v DO bloku s rollbackem před aplikací + post-apply test): admin AAL1 → `is_admin = false`, `profiles` vrátí jen vlastní řádek (1 z 13); admin AAL2 → `true`, všech 13; seller s AAL2 → false; bez JWT → false. Security advisors bez nových nálezů.
- • 7 nových testů (`jwt-aal` 5, AAL1-admin → 403 v `test-watchdog` a `lead-photos`) — **282 celkem, 25 suites**. tsc + build ✓.
- Právní dokumenty v2.1 — revize proti reálnému chování aplikace
- • **VOP**: nový čl. 4.4 moderace Poptávky (schválení/24h auto-approve/zrušení + odmítnutí bidů); platnost 30 dnů od zveřejnění + pravidla prodloužení (posledních 7 dní / po expiraci); odstraněny neexistující „2 rezervní Nabídky"; jeden **aktivní** Match + nový 7.7 (zrušený Match vrací Poptávku do oběhu — M-6); hodnocení: vyjádření makléře přes Provozovatele (self-service neexistuje); 12.4 + stav `contracted`; 13.3 povinné notifikace = match in-app + systémová (Check-in e-maily vypnutelné); vodoznak + EXIF strip fotek; SMS jen CZ/SK.
- • **RS makléře**: 4.2 schvalování registrace + výmaz zamítnutých po 7 dnech; 4.4 bez nepravdivého záznamu IP adresy (jen timestamp + verze); 11.4/11.5 hodnocení sladěno; 13.1/13.2 + `contracted`/`sale_reported`.
- • **Privacy**: SMS verifikace obou rolí; nový účel 3.18 bezpečnostní logy/rate limiting (IP, 24h); sekce 6 + EXIF strip a privátní úložiště fotek; 8.1 rozsah výmazu per Match; 8.5 retence snapshotu 1 rok/4 roky (M-9, dřív chybně „3 roky").
- • **Registrační modaly**: odstraněno nepravdivé „Veškeré zpracování probíhá v EU" — doplněny USA transfery (Resend, Sentry, Google) se SCCs/DPF.
- • LEGAL-*.md snapshoty synchronizovány s TSX. tsc + build ✓.
- FL-M14–M19: notifikační preference + dual-write, audit, telefon, auth guardy, únik ze spinneru
- • **FL-M14 — notifikační preference ignorovány / dual-write díry v match flow:** confirm-contract preference fetchoval a nepoužíval; report-sale sellera nenotifikoval VŮBEC (jen adminy); confirm-sale-seller posílal brokerovi jen in-app; email-action preference vůbec nefetchoval. → Nový sdílený helper `notifyCounterpart` (`lib/server/notify-counterpart.ts`): pref-gate (`cooperation` = smlouva, `sale_updates` = potvrzení prodeje; opt-out model) + dual-write in-app (`create_notification`) i e-mail (`sendNotificationEmail`, lookup adresy až PO pref checku). Nasazen ve všech 4 místech — webové routy i email-action větve teď produkují identické notifikace. Nová e-mail šablona `sale_confirmed_counterpart` (směr per `confirmed_by_role`, CTA z klíče příjemce — FL-H2 pravidlo, cena pokud nahlášena). Seller UI dostal řádek „Potvrzení prodeje" (`sale_updates`) — nový typ jde vypnout. Admin e-maily v email-action sjednoceny z `functions.invoke` na `sendNotificationEmail` helper.
- • **FL-M15 — zámek „match in-app nelze vypnout" byl jen v UI:** přímý PATCH `notification_preferences.match.inapp=false` (sloupec není v C2 zámku) notifikaci vypnul — `notify_match_created` preferenci četl, zatímco `handle_match_cancelled` posílal vždy (dva kontrakty na stejném typu). **Rozhodnutí: vynutit sender-side** — migrace odstranila oba `get_notification_pref(...,'match','inapp')` gaty z `notify_match_created` (in-app match = operační notifikace, posílá se VŽDY); e-mail gate zůstává, UI lock zůstává a nově říká pravdu. FL-H2 `match_id` payload nedotčen (ověřeno na živé DB: notifikace dorazily oběma stranám navzdory `inapp=false`).
- • **FL-M16 — submit-rating audit log se od M-4 tiše nezapisoval:** inline INSERT do `audit_logs` šel přes user-JWT klienta, ale M-4 zrušil authenticated INSERT policy → RLS zápis tiše zamítala (jen console.error). → `logAuditAction(supabaseAdmin, ...)` (vzor report-sale); `submit_rating` v ACTION_LABELS už byl.
- • **FL-M17 — telefon v profilu bez validace = trvalý zámek na verify:** profil ukládal RAW číslo (SMS EF ho pak odmítla → nekonečná verify smyčka bez úniku) a vymazání čísla nechalo `phone_verified_at` („ověřený" účet bez telefonu). → Nový helper `diffPhoneUpdate` (`lib/utils/phone.ts`): normalizace + validace jako při registraci, `changed` po normalizaci (přeformátování ≠ změna), vymazání nuluje i verified flag; inline hint v UI. Verify stránka nově umí ZMĚNIT existující neověřené číslo („Špatné číslo? Změnit číslo" → input mód, po uložení nový OTP flow) — úniková cesta z neověřitelného čísla.
- • **FL-M18 — tři stránky obcházely `useAuthGuard`:** `broker/bids`, `seller/leads` (ad-hoc `getUser()` + role check) a `broker/matches/[id]` (ani role check) — broker se zastaralými podmínkami mohl potvrdit smlouvu/nahlásit prodej, bez kontroly suspenze/verifikace/pending_approval. → Všechny tři na `useAuthGuard({ allowedRoles })`, fetch gated na `profile`, singleton `supabase` z guardu.
- • **FL-M19 — trvalé selhání načtení profilu = nekonečný spinner:** `fetchProfile` retryoval jen PGRST116 a jiné chyby nechaly `profile=null` bez signálu; `useAuthGuard` naplánoval jediný refetch a pak `isLoading=true` navždy; login stránka totéž. → Nové aditivní pole `profileError` v AuthContextu (23 konzumentů beze změny), guard po neúspěšném refetchi přesměruje na novou stránku `/auth/profile-error` („Zkusit znovu" = reload s auto-redirectem po úspěchu, „Odhlásit se" = poslední únik); login stránka spinner při chybě pouští a přesměruje tamtéž.
- • 29 nových testů (`notify-counterpart` 8, `submit-rating` 5, `report-sale` 4, `email-action` +5, `diffPhoneUpdate` 7) — **275 celkem, 24 suites**; `server-only` mock v setup.ts. tsc + eslint + build ✓.
4. července 2026
- FL-M6–M13: leads workflow — extend/cancel/edit hardening + admin UI + reálné počty bidů
- • **FL-M8 — zrušení leadu nechávalo bidy `pending` navždy a dialog lhal:** nový DB trigger `handle_lead_cancelled` (AFTER UPDATE `→cancelled`): pending/seen bidy → `rejected` + in-app notifikace dotčeným makléřům (typ `lead_cancelled`; rejected bidy nechává — M-6 evidence; accepted na cancelled leadu neexistuje). Spustí se i při GDPR cancelu. Dialog sellera nově počítá z `bidCount` RPC (dřív `bids.length` = před revealem 0) — slibované chování už je pravda.
- • **FL-M9 — seller dashboard + seznam ukazovaly 0/5 do revealu:** přímý SELECT z `bids` pod H-E RLS gate vracel 0 řádků → mrtvý „collecting" stav, skok 0/5→5/5. Nová bulk RPC `lead_bid_counts(uuid[])` (SECURITY DEFINER, non-rejected — v syncu s `lead_bid_count`; guard vlastnictví, REVOKE anon/public) nasazená v `dashboard/page.tsx` + `seller/leads/page.tsx`.
- • **FL-M10 — expirovaný lead s 1–4 bidy = slepá ulička s klamavým UI:** (a) lock screen u `expired` říká pravdu (nabídky se nezobrazí, CTA prodloužit/nová poptávka) místo „zobrazí se při 5" (na ne-aktivní lead bidovat nejde); text „makléři vaši poptávku vidí" jen u `active`. (b) extend počítá `max(now, stará expirace) + 14 dní` — extend leadu vypršelého před >14 dny už nevytvoří active lead s expirací v minulosti (CRON by ho hned vrátil a propálil jedinou šanci).
- • **FL-M11 — extend: mrtvá API route + pravidla nevynucená v DB:** reálný extend byl client-side UPDATE (dokumentovaný rate limit 5/min se neuplatnil, pravidlo „posledních 7 dní" jen v UI, seller mohl přímým PostgREST posouvat expiraci a resetovat flag). → H-D trigger nově pinuje `expires_at` + `extension_used` + `expiry_warning_sent_at` pro ne-admina a whitelist větev `expired→active` odstraněna; `/api/extend-lead` oživena jako JEDINÁ cesta (Bearer auth, validace včetně 7denního okna, zápis pod service_role, reset `expiry_warning_sent_at` — prodloužený lead zase dostane varování, audit). `handleExtendLead` volá route.
- • **FL-M13 — editace obsahu schváleného leadu nebyla blokována server-side:** H-D trigger pinuje obsahové sloupce (adresa, GPS, fotky, params, katastr, typ/kategorie, exclusivity) pro ne-admina, pokud `status NOT IN ('pending_review','rejected')` — odpovídá UI, edit/resubmit flow nedotčen.
- • **FL-M6 — admin leads UI s neexistujícím stavem `closed`:** volba = CHECK violation. Filtr seznamu přestavěn na reálný výčet (přidány `sold`/`cancelled`, `closed` pryč); badge sjednoceny do `lead-status-badge.tsx` (mrtvé case `closed`/`pending`/`accepted` odstraněny, `sold`/`cancelled` doplněny).
- • **FL-M7 — volný admin status-select bez pravidel:** `pending_review→active` selectem vytvářel nikdy neexpirující lead bez review metadat a notifikace. Nová mapa `VALID_LEAD_TRANSITIONS` (`lead-transitions.ts`): schválení/zamítnutí VÝHRADNĚ tlačítky, selectem jen `active→cancelled`; bez povolených přechodů se místo selectu zobrazí badge. `handleStatusChange` validuje + nově zapisuje audit log (`change_lead_status`).
- • 8 nových testů extend-lead route (246 celkem), tsc + eslint + build ✓.
- FL-M1–M5: email-action guardy + konzistence admin match workflow
- • **FL-M1 — email-action POST bez guardu na stav matche:** 14denní token zapsal potvrzení smlouvy/prodeje i do `cancelled`/`sold`/`invoiced`/`paid` matche a rozeslal notifikace protistraně/adminům. → Guard whitelist stavů per akce zrcadlí webové routy (`contract_confirm` → watching/contracted/sale_reported; `sale_confirm` broker → watching/contracted jako report-sale; seller → +sale_reported jako confirm-sale-seller). Mimo whitelist → `{ irrelevant: true }`, stránka `/email-action/success` zobrazí „Akce už není potřeba" (token zůstává záměrně spotřebovaný — koncový stav).
- • **FL-M2 — token se propálil při selhání akce:** selhání fetche matche vracelo 404 s už zkonzumovaným tokenem; `UPDATE matches` se nekontroloval → při chybě uživatel viděl úspěch, notifikace odešly, audit tvrdil `status_changed`, ale match se nezměnil a retry vracel „already". → Oba zápisy kontrolovány; při selhání se token vrátí (`responded_at = NULL`, helper `refundToken`) + 500 „zkuste znovu". Atomická konzumace před akcí (double-click ochrana) zůstává.
- • **Přibalený fix:** `email-action/page.tsx` POST handler četl `data.already` z nerozbaleného JSONu (`apiSuccess` balí do `{ data }`) → `already` větev z POSTu nikdy nefungovala (kryla ji jen GET pre-validace). Opraveno spolu s napojením `irrelevant`.
- • **FL-M3 — zamítnutí nahlášeného prodeje trvale vypnulo fáze C/D:** `handleRejectSaleReport`, `handleRevertToWatching` i inline select `sale_reported→watching` vracely status, ale nechávaly `sale_reported_at` (fáze C/D filtrují `IS NULL`) a `seller_confirmed_sale_at` (per-party skip sellera) → match už nikdy nedostal automatický sale check. → Všechna 3 místa nulují `sale_reported_at/price/date` + `seller_confirmed_sale_at/price` (admin rozhodl, že prodej nebyl — potvrzení sellera je tím také neplatné).
- • **FL-M4 — admin-set `contracted` nikdy nespustil fázi C:** inline select zapisoval jen `{status}`, ale fáze C kotví na `contract_signed_by_broker_at` → NULL = match navždy vyloučen ze sale checku. → Při přechodu na `contracted` se doplní `contract_signed_by_broker_at = now()` (jen pokud NULL — opakovaný přechod timestamp nepřepíše).
- • **FL-M5 — plomba box neakceschopný ve stavu, kdy plomba chodí:** watchdog detekuje plombu ve `watching`/`contracted`, ale akční tlačítka na detailu byla jen pro `sale_reported`/`sold` — text „potvrďte prodej níže" ukazoval na nic. → Nové tlačítko „Označit jako prodáno (plomba)" (`handleMarkSoldFromPlomba`: `sold` + `sale_detected_at` + cena z inputu, audit akce `mark_sold_plomba`); navazuje existující „Potvrdit prodej".
- • 8 nových testů POST /api/email-action (guardy, refund, race, expirace — 238 celkem), tsc + eslint + build ✓.
3. července 2026
- FL-H3 + FL-H4: Karta spolupráce mizela ve stavu `contracted` + seller sám měnil stav matche
- • **Broker UI** (`section-cooperation.tsx` + hook): karta pro `watching`/`contracted`/`sale_reported`; formulář „Nahlásit prodej" ve `watching` i `contracted`; nový info box v `sale_reported` („Prodej byl nahlášen a čeká na ověření administrátorem").
- • **Seller UI** (`section-collaboration.tsx`): karta + blok „Prodej" pro `watching`/`contracted`/`sale_reported` (potvrzení sellera status nemění, jde o korroboraci).
- • **`confirm-sale-seller`**: zapisuje JEN `seller_confirmed_sale_at` (+ volitelná cena) — status mění výhradně brokerův report nebo admin; audit loguje skutečný zápis. Admin viditelnost beze změny (box „Potvrzeno prodávajícím" + notifikace adminům existovaly už dřív). Komentář v email-action aktualizován (větve jsou nyní sjednocené).
- • **checkin-send v13** (doplněk, vyřešen i LOW nález z auditu): fáze C/D sale check per-party skip — seller, který prodej už potvrdil, se znovu neobesílá každé 3 měsíce; broker ano, dokud nenahlásí. Nasazeno přes CLI (verify_jwt=false), smoke test 200 OK (`{success:true, ...}`).
- • tsc + eslint + build + 230 testů ✓.
- FL-H2: Deep-linky brokera vedly na seznam místo detailu zakázky + vadné e-mail CTA
- • **Stránka překlíčována na `match_id`** (`use-broker-match-detail.ts`): dual lookup — primárně `.eq("id", param)`, fallback `.eq("lead_id", param)` pro legacy odkaz z `/broker/bids` (UNIQUE(lead_id, broker_id) garantuje ≤1 match). Tím se ~7 send sites opravilo bez zásahu (většina už match.id posílala).
- • **`match_broker` CTA**: trigger `notify_match_created` nově posílá `match_id` v datech e-mailu (migrace); šablona `match.ts` linkuje `/broker/matches/{match_id}` s fallbackem `/broker/bids?filter=accepted`. Nová stránka `/broker/matches` (bez ID) = redirect na seznam akceptovaných nabídek (kryje případné staré e-maily).
- • **`contract_signed_notification`**: `confirm-contract` a `email-action` posílají jen klíč příjemce (potvrdil seller → broker dostane `match_id`; potvrdil broker → seller `lead_id`) — šablona beze změny, ternár teď vybírá správně.
- • **Přibalený fix (nález z auditu, chyběl v TODO):** seller detail — match dotaz `.eq("lead_id", ...)` doplněn o `.neq("status","cancelled")` + `.maybeSingle()`; po M-6 může mít lead zrušený i nový match a `.single()` by spadl na 2 řádcích (seller by ztratil match UI).
- • **Ověřeno:** živá DB (DO blok + rollback) — in-app notifikace brokera má href `/broker/matches/<match_id>`, e-mail payload `match_broker` obsahuje `match_id`, lead po create_match `matched` (FL-K1 sanity). EF v43 nasazena přes CLI (`verify_jwt=false` zachováno, ověřeno přes list_edge_functions). tsc + eslint + build + 230 testů ✓.
- FL-H1: Rozjeté počítání bidů rozbíjelo 2. kolo po zrušení matche + anon četl view leadů
- • **View**: obě `count(*)` subquery (sloupec `bid_count` + WHERE) → `AND status != 'rejected'`; CREATE OR REPLACE (zachová granty), `security_invoker=false` beze změny (C3 column-masking boundary). Komentář: musí zůstat v syncu s `lead_bid_count`/`check_bid_limits`. Brokerům se tím opraví i „X/5 obsazeno" na kartě leadu po reopenu.
- • **`notify_new_bid`**: `COUNT(*)` → `lead_bid_count(NEW.lead_id)` (single source of truth) — notifikace „Nabídky jsou připraveny!" přijde přesně při 5. reálné nabídce, jednou.
- • **Vedlejší nález (opraven tamtéž):** `anon` měl na view VŠECHNA práva — C3 migrace dělala DROP+CREATE a Supabase **default privileges** při CREATE znovu přidaly granty pro anon/authenticated/service_role (původní `GRANT SELECT TO authenticated` záměr tak byl tiše přepsán). Kdokoli s public anon klíčem (je ve frontend bundle) mohl bez přihlášení číst anonymizované aktivní leady. → `REVOKE ALL FROM anon` + REVOKE ne-SELECT práv authenticated. **Gotcha do budoucna: po DROP+CREATE view/tabulky vždy zkontrolovat granty.**
- • **Ověřeno na živé DB (DO blok + rollback, simulace 2. kola se 7 brokery):** evidence bid → `bid_count=0`, lead viditelný; po 4 nových bidech viditelný, žádná notifikace; 5. bid → view skryje, `lead_bid_count=5` (reveal gate), přesně 1 notifikace; 6. bid odmítnut `check_bid_limits`; granty anon=false/authenticated SELECT only; `security_invoker=false` zachováno. Frontend beze změn (jen dostává správné hodnoty).
- Audit logičnosti flow + KRITICKÝ fix FL-K1: výběr makléře nefungoval (create_match vs. H-D trigger)
- • **Fix (stavový důkaz, bez GUC):** trigger povolí `active→matched` ne-adminovi JEN pokud pro lead existuje ne-zrušený match — `EXISTS (... matches WHERE lead_id = OLD.id AND status != 'cancelled')` (kryje partial index `matches_lead_id_one_active`). `create_match` vkládá match PŘED `UPDATE leads` (pořadí je nosné, komentováno). Seller stav nezfalšuje: `matches` nemá žádnou INSERT policy (klientský INSERT RLS zamítne — **NEPŘIDÁVAT**, výjimka na tom stojí).
- • **FL-M12 — guardy v `create_match`:** `FOR UPDATE OF l` zámek řádku leads (serializace souběžných akceptací — druhá skončí čistou chybou, ne unique violation; stejné pořadí zámků jako `check_bid_limits` → bez deadlocku), guard na neexistující bid (dřív NULL-unsafe `!=` check proklouzl), NULL-safe ownership (backend/admin/vlastník), guard `lead.status='active'` (nelze matchnout expirovaný lead přímým RPC), whitelist stavů bidu `pending`/`seen` (evidence `rejected` bid po M-6 zrušení nelze re-matchnout; `accepted` → „již byla přijata"). `SET search_path TO ''` + plná kvalifikace (konvence).
- • **P1 (nalezeno při plánování):** `anon` měl na `create_match` EXECUTE (default PUBLIC grant) — anonymní PostgREST request má `auth.uid()` NULL, takže backend větev by byla zneužitelná → `REVOKE EXECUTE FROM anon, public; GRANT TO authenticated, service_role`.
- • **Frontend:** `handleAcceptBid` (`use-lead-detail.ts`) místo zahazování chyby mapuje hlášky DB guardů na srozumitelné chyby (vzor `check_bid_limits` mapování v broker detailu).
- • **Opravou se obnovila 3 rozbitá navazující chování:** reveal bidů sellerovi po matchi (RLS větev `matched`), uzavření leadu pro další bidy (`check_bid_limits` vidí `matched`) a funkční M-6 reopen po zrušení matche.
- • **Ověřeno na živé DB (DO blok + rollback, 8 testů):** happy path pod seller JWT (lead `matched`, bid accepted, ostatní rejected, match vznikl), druhá akceptace → „Poptávka již není aktivní", M-6 cancel → lead zpět `active` + evidence bid + re-match blokován, přímý seller UPDATE `active→matched` bez matche dál tiše blokován, expirovaný lead / cizí seller / neexistující bid → správné chyby, granty anon=false/authenticated=true. Snapshot `supabase/reference/schema.sql` aktualizován. tsc + eslint + build + testy ✓.
- Fix: 1-click odkazy z e-mailů nefungovaly pro nepřihlášené uživatele
- • **Nový `GET /api/email-action?token=...`**: server-side validace tokenu pod service_role (vzor existujícího POST) — vrací jen minimum pro zobrazení (`action_type`, `location`, `already`, `expired`). Stejný rate limit bucket jako POST (10/min).
- • **`email-action/page.tsx`**: přímý Supabase dotaz nahrazen fetchem na GET endpoint; RLS zůstává zamčené beze změny (žádná nová policy).
- • E2E ověřeno na produkci: fáze A (contract prompt 7d), B (reminder 21d), C (sale check 42d), D (opakovaný 3m) + 1-click potvrzení smlouvy/prodeje oběma rolemi, idempotence opakovaného kliknutí, pravidlo důvěryhodnosti (seller sám stav nemění), neplatný token 404.
- • 6 nových testů (230 celkem), tsc + eslint + build ✓.
- Pre-launch audit — M-1: Rate limit obejitelný přes podvrženou IP + in-memory stav
- • **(A) `getClientIp` trusted-hop model** (`frontend/src/lib/rate-limit.ts`): reálná IP klienta se bere z `x-forwarded-for` na pozici `len - TRUSTED_PROXY_HOPS` (env, default 1 = Traefik, který reálnou IP klienta připojuje jako poslední záznam) — vše nalevo je klientem podvržené a ignoruje se. Vytažená hodnota se validuje přes nový `isValidIp` (IPv4/IPv6). `x-real-ip` je jen fallback (za proxy), NE primární zdroj — Traefik ji nemusí nastavovat a klientem zaslaná by byla spoofovatelná. Nevalidní/chybějící důvěryhodný hop → fail-closed na sdílený `unknown` bucket (přísnější, ne obejitelný). Připraveno na budoucí Cloudflare (`TRUSTED_PROXY_HOPS=2` nebo `CF-Connecting-IP`).
- • **(B) DB-backed rate limit pro 5 kritických endpointů**: nový helper `lib/rate-limit-db.ts` (`checkRateLimitDb`) počítá requesty v okně přes tabulku `api_rate_limit` — na rozdíl od in-memory přežije deploy a je sdílený napříč instancemi. Nasazeno na `verify-email` (brute-force tokenu), `check-availability` (enumerace), `support-ticket` (spam), `ico-lookup` + `cadastral-lookup` (pálení kvót ARES/ČÚZK). **fail-open + Sentry** při chybě DB (dostupnost webu > striktnost limitu). Typeahead/UX endpointy (`address-suggest`, `reverse-geocode`, `lead-photos`, `audit-log`) zůstávají in-memory (DB zápis per stisk klávesy je nežádoucí).
- • **Tabulka `api_rate_limit`** (`bucket_key`, `endpoint`, `hit_at`): RLS enabled **bez policies** → přístup jen service_role; index `(endpoint, bucket_key, hit_at)`; cleanup CRON `cleanup-api-rate-limit` denně 5:00 UTC (záznamy starší 24h). Ověřeno na živé DB (RLS on, index, 0 policies, cron `0 5 * * *`).
- • Známý přijatý tradeoff: count-then-insert není atomické (TOCTOU) — pro rate limiting akceptovatelné (krátký burst přes limit). Nový env `TRUSTED_PROXY_HOPS=1` v `.env.example`. 14 nových testů (224 celkem), tsc + eslint + build ✓.
- • **Infra ověřeno v produkci (3. 7.):** `TRUSTED_PROXY_HOPS=1` nastaveno v Coolify; dočasnou route `debug-clientip` změřeno, že `x-forwarded-for` nese jediný hop (Traefik) = reálná IP klienta; spoof test z externího stroje potvrdil, že Traefik **přepisuje** `x-forwarded-for` i `x-real-ip` reálnou IP → podvržené hlavičky se do appky nedostanou. Měřicí route poté smazána. Zbývá jen ověřit, že Next.js kontejner není dosažitelný napřímo mimo Traefik (`docker ps` / `nmap`).
2. července 2026
- Pre-launch audit — M-6: Zrušený match natrvalo zablokoval lead
- • **Částečný unikátní index** `matches_lead_id_one_active` (`WHERE status != 'cancelled'`) místo plného `UNIQUE(lead_id)` — zrušené matche zůstávají v historii (důkazy), nový match je po zrušení povolen; dva aktivní matche na jeden lead dál nemožné.
- • **Nový trigger `on_match_cancelled_reopen_lead`** (AFTER UPDATE na `matches`): při přechodu `watching/contracted/sale_reported → cancelled` vrátí lead do oběhu — status `active`, čerstvá expirace +30 dní, reset `extension_used`/`expiry_warning_sent_at`. Ze `sold/invoiced/paid` se lead NEvrací (nemovitost prodána, řeší admin ručně).
- • **Reset slotů (2. kolo)**: nabídka zrušeného makléře → `rejected` (zůstává jako důkaz — FK z matches, ON DELETE CASCADE by smazal match!); ostatní zamítnuté nabídky bez vazby na match se **mažou** → sloty 0/5, původní makléři mohou nabídnout znovu (zrušený makléř ne — `UNIQUE(lead_id, broker_id)`). Rozhodnutí 2026-07-02.
- • **`lead_bid_count()` počítá jen non-rejected** — sjednocení s `check_bid_limits`; bez toho by reveal policy po resetu odhalila nové nabídky okamžitě (staré rejected bidy splnily `>= max_bids`) → rozbité FOMO 2. kola. Běžný flow beze změny (před matchem rejected bidy neexistují).
- • **GDPR guard**: seller se smazaným účtem (`deleted_at`) → lead se místo `active` zruší (anonymizovaný lead se nikdy znovu nepublikuje) a notifikace se neposílá.
- • **Notifikace**: in-app sellerovi („poptávka je znovu aktivní", deep-link na detail). Makléřům se záměrně nic neposílá — lead se tiše objeví v seznamu (rozhodnutí 2026-07-02). Admin UI: dialog zrušení + toast informují o vrácení leadu do oběhu.
- • Ověřeno na živé DB (DO blok s vynuceným rollbackem): labely, blokace 2. aktivního matche, revert leadu (+30d, sloty 0/5, 4 bidy smazány, bid matche rejected, notifikace), rebid původního makléře + nový match po zrušení, GDPR guard větev. tsc + eslint + build + 210 testů ✓.
- Pre-launch audit — M-11: Cenový semafor se zapisoval už od 1./3. nabídky
- • Starý trigger + jeho argless funkce `calculate_price_label()` odstraněny; smazány i 2 osiřelé trigger funkce (`trigger_calculate_price_label`, `trigger_recalculate_lead_price_labels`), na které žádný trigger neukazoval.
- • Zůstává jediný mechanismus: `tr_calculate_price_labels_at_max_bids` — při dosažení `max_bids` hromadný přepočet všech nabídek (medián ze všech non-rejected, ±15 %). Rozšířen o `UPDATE OF price_min, price_max` (admin editace ceny po revealu nesmí nechat zastaralé labely).
- • Datový fix: vynulovány předčasné labely na leadech pod `max_bids`; leady na plných slotech přepočítány novou logikou; matched leady beze změny (historicky zobrazené labely).
- • Frontend beze změny — před revealem jsou labely `NULL`, badge se nezobrazí.
21. června 2026
- Fix — Google OAuth (login i registrace) přesměroval na localhost
- • **Důkaz:** návštěva `https://spravnymakler.cz/auth/callback` (bez `code`) → 307 na `https://localhost:3000/auth/login?error=missing_code`.
- • **Nový helper `lib/site-url.ts`** — `SITE_URL` = `NEXT_PUBLIC_SITE_URL` (override pro dev) s produkčním fallbackem `https://spravnymakler.cz`. Záměrně **NE** z `x-forwarded-host` (lze podvrhnout → open-redirect/phishing).
- • **Opraveny 3 route handlery** (stejný vzor): `auth/callback/route.ts` (OAuth — 7 redirectů, `origin = SITE_URL`), `auth/verify-email/route.ts` (ověření e-mailu — 4 redirecty, base `SITE_URL`), `api/checkin-respond/route.ts` (legacy F10 — 6 redirectů). Čtení query params z requestu zůstává z `request.url`.
- • **Env:** `NEXT_PUBLIC_SITE_URL=http://localhost:3000` přidáno do `.env.local` (dev — aby lokální OAuth dál mířil na localhost) i `.env.example`. Produkce běží přes hardcoded fallback (Coolify env nutný není).
- • **Deploy:** vyžaduje redeploy frontendu na Coolify. tsc/eslint + `next build` ✓.
12. června 2026
- Pre-launch audit — GDPR trojice M-7 + M-8 + M-9 (delete-account)
- • `support_tickets` — DELETE (řádky uživatele + anonymní tickety se stejným e-mailem z kontaktního formuláře); jméno/e-mail/text bez důkazní hodnoty (rozhodnutí: smazat úplně).
- • `email_action_tokens` — DELETE (živé 1-click contract/sale tokeny by jinak fungovaly i po smazání účtu).
- • `broker_ratings.comment` — u sellera vyčištěn na NULL (volný text může identifikovat); číselná hodnocení zůstávají (agregát pro profil makléře).
- • `auth.identities` — nová SECURITY DEFINER funkce `scrub_auth_identities` redukuje `identity_data` na `{sub, deleted_e-mail}` (e-mail identita držela jméno/telefon/IČO/firmu z registrace, Google navíc avatar/full_name). `sub` zůstává (opaque ID pro GoTrue konzistenci, uživatel je zabanovaný) — akceptované reziduum.
- • `audit_logs` se ZÁMĚRNĚ nedotýkáme (právní důvod, self-contained metadata).
- • Ověřeno na živé DB (transakce + rollback): `scrub_auth_identities` zredukoval identity (PII pryč, `sub` zachován); snapshot insert s `purge_after` +1 rok pro 0 matchů; nové CRON joby zaregistrovány. OTP šablona ověřena doručeným e-mailem. tsc + eslint + build + 210 testů ✓. (OAuth-only detekci nelze živě vyvolat — Google účet nejde přes API; ověřeno code review.)
- Pre-launch audit — M-3: Watchdog nastavoval `sold` při JAKÉkoli plombě
- • **`watchdog-check` v19**: detekce **NEmění status** — zapisuje `plomba_detected_at` + `plomba_typ_rizeni` + `plomba_rizeni_id` (sloupce existovaly, kód je neplnil; admin UI je už zobrazovalo). Admin rozhodne přes existující `confirm_sale`. **Dedup přes MNOŽINU řízení** (`plomba_rizeni_id` = CSV všech běžících řízení): na LV může běžet víc plomb současně (např. hypotéka + následný prodej) — každé dosud neviděné řízení vyvolá novou notifikaci, stejná množina se nehlásí každé pondělí; dokončená řízení se z uloženého seznamu tiše odmazávají. `plomba_typ_rizeni` = unikátní typy všech řízení. Bonus: plní `last_watchdog_check` u každé kontroly (observabilita, admin list ho zobrazuje).
- • **Šablona `plomba_detected`** (send-notification-email v41): odstraněno „Status byl automaticky změněn na Prodáno" → „status NEBYL změněn, prověřte typ řízení na LV"; kroky přepsány (prodej → potvrdit v admin panelu, jinak nechat — watchdog sleduje dál); tlačítko deep-link na `/admin/matches/{id}`.
- • **Admin UI**: plomba box v detailu matche doplněn o výzvu „Prověřte na LV — pokud jde o převod vlastnictví, potvrďte prodej."
- • Deploy obou EF (CLI). Ověřeno živým během přes `net.http_post`+Vault: checked 2, statusy zůstaly `watching`, `last_watchdog_check` zapsán. Detekční větev ověřena code-review (reálnou plombu nelze vyvolat na povel). tsc + build ✓.
- Pre-launch audit — M-2: HTML injection v e-mailových šablonách
- • **Escaping na hranici** (jeden chokepoint, šablony beze změny): nový `send-notification-email/html.ts` — `escapeHtml()` (`& < > " '` → entity) + `escapeDataStrings(data)` (jen stringy; čísla zůstávají kvůli `.toLocaleString()`). `index.ts` v24: `template.html(escapeDataStrings(payload.data), escapeHtml(greetingName))`. Subjecty jsou statické — netýká se jich.
- • URL kontexty bezpečné: HTML entity v atributech jsou validní (`&` v href se dekóduje), UUID tokeny beze změny, ISO datumy pro `new Date()` escaping nemění.
- • **Deploy v40** (CLI). **Ověřeno end-to-end doručeným e-mailem**: testovací payload s `<h2>`, falešným `<a href>` a pokusem o útěk z atributu (`"><img onerror=...>`) → v doručeném HTML vše escapované, tagy se zobrazují doslova jako text, žádný klikací odkaz. Žádné frontend změny.
- Pre-launch audit — DB dávka M-4 + M-5 + M-12 (audit log, bids, anti-bypass)
- • Policy zrušena → INSERT výhradně **service_role**. Nová proxy **`POST /api/audit-log`** pro client-side zápisy: identita + role odvozeny server-side z JWT + `profiles` (klient je neposílá), `action` proti whitelistu `ACTION_LABELS`, payload limity (100 znaků / 8 KB), rate limit 30/min.
- • `lib/audit.ts` (server) nově vyžaduje service_role klienta — 5 API routes přepnuto na `supabaseAdmin` (extend-lead, report-sale, confirm-contract, confirm-sale-seller, checkin-respond-web). Nový **`lib/audit-client.ts`** (`logAuditClientAction`) — ~25 client callsites ve 12 souborech přepnuto, nepotřebné `auth.getUser()` fetche odstraněny.
- • **Akceptováno**: uživatel může zalogovat whitelistovanou akci s vymyšlenými metadaty pod vlastní identitou — kritické monetizační akce se logují server-side v routes.
- • Migrace `20260612131655_m4_m5_m12_audit_bids_filter_hardening.sql`. Ověřeno na živé DB (transakce + rollback): authenticated INSERT zamítnut; broker self-accept = 0 řádků + create_match OK; telefon v `services_other` → `[tel. odstraněn]` + flag. 9 nových testů (210 celkem), tsc + eslint + build ✓.
- Pre-launch audit — G-8: Deanonymizace leadů přes fotografie + M-10 EXIF fallback
- • **Bucket → private** (migrace `20260612093432_g8_private_property_photos_bucket.sql`). Public URL v DB zůstávají jen jako kanonické identifikátory; `delete-account` EF funguje beze změny (extrahuje cestu splitem). Bonus: INSERT policy zpřísněna na vlastní složku (`(storage.foldername(name))[1] = auth.uid()`, dřív šlo nahrát kamkoli v bucketu).
- • **Nová API route `GET /api/lead-photos?leadId=`** — Bearer JWT + autorizační matice: admin/seller(vlastník)/broker-s-matchem → interiér+exteriér; broker bez matche → JEN interiér a JEN u leadu viditelného přes `leads_for_brokers` (active + volné sloty); jinak 403. Podepisuje batch `createSignedUrls` pod service_role, expirace 1 h. Rate limit 20/min.
- • **Hook `useSignedPhotos(leadId)`** — mapa kanonická → signed URL; nasazen v 6 zobrazovacích místech (seller detail, seller edit, broker lead detail, broker match detail, admin detail ×2 galerie). `PhotoUpload` dostal `displayUrls` prop (edit) + module-level blob preview cache pro čerstvé uploady (přežívá unmount wizard kroků; `getLocalPhotoPreview` pro step-5 souhrn).
- • **Watermark při uploadu** (`lib/utils/watermark.ts`) — centrované logo SM (`frontend/public/watermark-logo.svg`), 50 % šířky, opacity 0.2, rotace −20°, bílý tint + stín (viditelné na světlých i tmavých fotkách). Aplikuje se po kompresi, před uploadem; při selhání se soubor zamítne. Stávající (testovací) fotky bez watermarku — akceptováno.
- • **Varování pro sellery** v upload UI: nepoužívat fotky ze starších inzerátů (jediný vektor, kde reverse search najde adresu).
- • **M-10 — EXIF fallback**: při selhání canvas komprese (= EXIF/GPS strip) se dřív nahrál **originál s GPS polohou nemovitosti**. Nově se soubor zamítne s chybovou hláškou — neočištěný soubor nikdy neopustí prohlížeč.
- • **Akceptováno vědomě**: `userId` v cestě signed URL (jen korelace, profil přes RLS nepřečtitelný); reverse search přihlášeným brokerem na dřív zveřejněných fotkách (inherentní — mitigace varování + watermark + identifikovaný broker se smluvním postihem).
- • Verifikace: 12 nových vitest testů autorizační matice (201 celkem) ✓, tsc ✓, build ✓; živě ověřeno — public URL fotky vrací 400.
11. června 2026
- Pre-launch audit HIGH — Skupina 6: Dependency audit + Next.js (H-G)
- • `npm audit fix` (non-force) — vyčistil **high** transitivní (`fast-uri`, `minimatch`) + moderate (`ajv`, `brace-expansion`).
- • `next` 16.1.6 → **16.2.9** (latest stable; `package.json` floor `^16.1.6`→`^16.2.9`) — vyčistilo **původní HIGH next advisories** (HTTP request smuggling v rewrites, Server Actions CSRF přes null origin, DoS Server Components).
- • **Zbývá 2 moderate** (next + jeho bundled `postcss`, GHSA-qx2v-qp2m-jg93) — fix je až **next ≥16.3.0**, který existuje jen jako **canary**. `npm audit fix --force` by degradoval next na 9.3.3 (breaking) → NEPOUŽITO. Část advisories navíc neplatí (projekt nemá `middleware.ts`, `unoptimized:true`).
- • **Tracked post-launch**: upgrade next na ≥16.3.0 až vyjde stable.
- • Verifikace: `tsc` ✓ (pozn.: tsc musí běžet po `next build` — jinak kontroluje zastaralé `.next/types`), 189 testů ✓, `next build` ✓. Změna jen `package.json` + `package-lock.json`.
- Pre-launch audit HIGH — Skupina 5: SMS rate limit (H-B)
- • Nová tabulka `sms_attempts (user_id, phone, kind, attempted_at)`, RLS enabled BEZ policies (jen service_role) + 2 indexy + `cleanup_sms_attempts()` CRON denně 4:15 UTC. Migrace `20260611160155_sms_attempts_rate_limit.sql`.
- • Edge Function `send-verification-sms` v13→v14:
- • Ověřeno na živé DB: tabulka + RLS bez policies + CRON zaregistrovány; count logika (cooldown/24h okno) sedí. Žádné frontend změny.
- Pre-launch audit HIGH — Skupina 4: GDPR výmaz adres + fotek (H-F)
- • **Mazat (adresa + fotky) JEN leady BEZ matche** — čistá PII bez zakázky. Matchnuté leady (jakýkoli stav vč. `watching`) zůstávají KOMPLETNÍ: důkaz pro vymáhání provize + watchdog potřebuje `cadastral_code`/`parcel_number`.
- • **Watching matche se ZÁMĚRNĚ neruší** (dřív krok 7d je cancelnul) — jinak by se zabil watchdog a při prodeji po smazání by platforma přišla o provizi. Nárok je vůči makléři (anonymizace sellera nevadí, identita v `*_snapshot`).
- • Fotky se mažou per-lead z polí `photos_interior`/`photos_exterior` (split na `/property-photos/`) → ve sdílené složce `${userId}/temp/` zůstanou fotky matchnutých leadů nedotčené (NE prefix-list).
- • Anonymizace: `exact_address=""`, `street/zip/lat/long/display_*/cadastral_*/parcel/house/lv/unit=null`, photos `[]`. service_role obejde RLS + H-D trigger.
- • Edge Function `delete-account` v18→v19 (CLI). Response + log doplněn o `leads_anonymized`/`photos_deleted`. Ověřeno na živé DB (transakce + rollback): seller se 3 leady (1 nematchnutý + 2 matchnuté) → smazán jen nematchnutý (`exact_address=''`), matchnuté nedotčené vč. `cadastral_code` (watchdog OK). Žádné frontend změny.
- Pre-launch audit HIGH — Skupina 3: Anonymizace nabídek před matchem (H-E)
- • **`lead_bid_count(p_lead_id)`** — SECURITY DEFINER počítadlo (obchází RLS; naivní `count(*)` uvnitř bids policy = rekurze). Použito v policy i jako RPC pro lock screen.
- • **`bids` SELECT policy** — seller větev gated revealem: `status='matched' OR lead_bid_count(lead_id) >= max_bids`. Před revealem seller nevidí žádný bid ani přímým REST.
- • **`profiles` SELECT policy** — nahrazena klauzule „seller vidí bidujícího makléře" za „seller vidí **matchnutého** makléře" (matches-join). Před matchem profil makléře nedostupný → embed `broker` null.
- • Frontend: `use-lead-detail.ts` (RPC `lead_bid_count` → `bidCount`, reveal/lock z bidCount místo `bids.length`), `section-bids.tsx` (empty/lock/reveal podle bidCount), `BidWithBroker.broker` nullable, `bid-comparison-table` + `section-match-info` null-guard (identita jen post-match, jinak „#N"). Tabulka už labelovala `#N`.
- • Migrace `20260611142241_anonymize_bids_and_broker_pre_match.sql`. Ověřeno na živé DB (`SET ROLE authenticated` + rollback): pre-reveal seller vidí 0 bidů (count RPC vrací správně), po revealu vidí bidy, a z 5 bidujících vidí profil JEN matchnutých (nematchnutí skrytí). tsc + 189 testů + build ✓.
- Pre-launch audit HIGH — Skupina 2: Leads column-lock (H-D)
- • Oprava: trigger `protect_lead_privileged_columns` (BEFORE INSERT OR UPDATE). Admin (`is_admin`) a backend (`auth.uid() NULL`) bypass.
- • Migrace `20260611140728_protect_lead_privileged_columns.sql`. Ověřeno na živé DB (transakce s rollbackem): UPDATE `pending_review→active`+`max_bids=99` blokováno; cancel/extend/resubmit prošly; resubmit deadline přepsán z minulosti na now+24h; INSERT `status='active'`/`max_bids=99` vznikl jako `pending_review`/5.
- Pre-launch audit HIGH — Skupina 1: SECURITY DEFINER funkce + emaily (H-A + H-C)
- • Oprava funkce: guard `IF auth.uid() IS NOT NULL AND NOT is_admin(auth.uid())` — povolí service_role/CRON (`uid NULL`) i admina, blokuje běžného uživatele. Param sjednocen na `user_id`.
- • Callery sjednoceny na `user_id`: checkin-send (×6), expire-leads (×2), watchdog-check (×1), confirm-contract (×2), confirm-sale-seller (×1), report-sale (×1), email-action (×3).
- • Ověřeno na živé DB: pod service_role kontextem vrací email.
- • Oprava: guard `IF pg_trigger_depth() = 0 AND auth.uid() IS NOT NULL AND auth.uid() <> p_user_id AND NOT is_admin(auth.uid()) THEN RAISE`. `pg_trigger_depth()>0` zajistí, že SQL trigger dual-writes (`notify_new_bid`, `notify_match_created`, ...) projdou — jinak by se rozbil INSERT bidu/matche. service_role/CRON (`uid NULL`), admin a self projdou.
- • Cross-user volání z API routes přepnuta na service_role: `confirm-contract`, `confirm-sale-seller`, `report-sale`, `submit-rating` (nový `supabaseAdmin`), `lib/checkin-utils.ts`. Vedlejší přínos: admin notifikace v těchto routes dřív nefungovaly vůbec (seller/broker přes RLS nepřečte admin profily) — nyní fungují.
- • Ověřeno na živé DB (transakce s rollbackem): přímá cross-user injekce blokována, volání z trigger kontextu prošlo (dual-write bezpečný).
- • Migrace `20260611133822_harden_definer_funcs_email_and_notification.sql`. Redeploy Edge Functions: `checkin-send` (v12, MCP), `expire-leads` + `watchdog-check` (CLI). Verifikace: tsc + 189 testů + eslint + build.
- Pre-launch audit: oprava kritických blokerů (C1–C4 + matches RLS)
- • Migrace `20260611103658_fix_handle_new_user_role_whitelist.sql`
- • Migrace `20260611103709_protect_profile_privileged_columns.sql`
- • Migrace `20260611103724_anonymize_leads_view_definer.sql`
- • **Akceptovaný tradeoff:** vrací 1 Supabase advisor `security_definer_view` (ERROR) — vědomě, jde o záměrný anonymizační boundary.
- • Migrace `20260611103734_fix_cleanup_unverified_condition.sql`
- • Migrace `20260611103742_matches_update_admin_only.sql`
- • Routes: `report-sale`, `confirm-contract`, `confirm-sale-seller`, `lib/checkin-utils.ts` — zápis do `matches` přes service_role po server-side autorizaci.
9. května 2026
- Cleanup: odstranění staging password feature
- • `frontend/src/app/auth/staging/page.tsx` — login formulář
- • `frontend/src/app/api/staging-auth/route.ts` — API endpoint nastavující cookie
- • `stagingAuth` rate limit z `lib/rate-limit.ts`
- • `CLAUDE.md`, `LEGAL-PRIVACY.md`, `TESTING-MATRIX.md/.csv`, `.claude/docs/ROUTES-AND-STRUCTURE.md`, `.claude/docs/COMPLETED-FEATURES.md` — odstraněny zmínky o staging gate
- • `LEGAL-PRIVACY.md` — odstraněna deklarace staging cookie ze sekce 9.1 (právní dokument nesmí deklarovat cookie, kterou neukládáme)
19. dubna 2026
- Security: quick wins z auditu (M2, H4, M4)
- • **M2 — sendNotification rate limit 20→5/min** (`lib/rate-limit.ts:114`): Admin akce limit zpřísněn (anti-spam). Legitimní use case (admin posílá emaily uživatelům) běží daleko pod limitem.
- • **H4 — `supabase/config.toml`**: Nový soubor trackuje `verify_jwt` flag pro všech 8 Edge Functions v repo (jediný source of truth, prevence driftu mezi platform UI a kódem). Inventura přes Supabase MCP potvrdila, že stav v UI odpovídá: `send-notification-email/watchdog-check/expire-leads/checkin-send/auto-approve-leads` mají `false` (s vlastním Bearer checkem), `delete-account/send-verification-email/send-verification-sms` mají `true`.
- • **M4 — anti-bruteforce na `delete-account`**: Self-service mode volal `signInWithPassword()` bez per-IP ochrany → bruteforce risk. DB-backed rate limit (perzistentní napříč Edge Function instances).
- • **M1 (Mapy.cz error sanitizace) — uzavřeno jako false positive**: Po revizi `address-suggest` a `reverse-geocode` — oba routes vrací generic `apiError(...)` při `!response.ok`. Mapy.cz API key je v URL query, ne v response body, takže `{...data, source}` spread není vektor leaku.
- Security: oprava 4 nálezů z bezpečnostního auditu (C1, C2, H1, H2)
- • **C1 — `send-notification-email` Bearer auth check (CRITICAL)**: Edge Function měla `verify_jwt: false` BEZ vlastního auth checku → publicly callable. Kdokoliv s URL mohl posílat emaily z naší domény (`notifikace@spravnymakler.cz`) — riziko spamu/phishingu. Fix:
- • **C2 — `useAuthGuard` MFA fail-closed (CRITICAL)**: Pokud `getAuthenticatorAssuranceLevel()` nebo `listFactors()` selhalo (síťová chyba, Supabase timeout), kód spadl do `setIsAuthorized(true)` → admin dostal plný přístup BEZ 2FA verifikace. Fix:
- • **H1 — `/api/send-notification` sanitizace error (HIGH)**: Proxy vracela `errorText` z Edge Function přímo klientovi (`apiError(\`...${errorText}\`, ...)`) → potenciální leak interních detailů Resend/SDK chyb. Fix: generic `"Email se nepodařilo odeslat."` + 502 status, detail jen do `console.error`.
- • **H2 — `send-notification-email` sanitizace error (HIGH)**: Edge Function vracela `error.message` (Resend) a `String(error)` (catch) v JSON response. Fix: generic `"Email send failed"` (502) a `"Internal server error"` (500), detail jen do `console.error`.
- Refactor: property-params-section split + memoizace
- • Původní monolitický `property-params-section.tsx` (543 ř.) rozdělen do `property-params-section/`:
- • `activeAmenities`, `activeHouseChars`, `unmanagedEntries`, `currentEncumbrances` nyní přes `useMemo` — dříve se přepočítávaly na KAŽDÝ render (včetně každého znaku při psaní do inputu)
- • `has()` predikát stabilizován přes `useCallback` → umožňuje memoizaci derivovaných seznamů
- • `setVal` stabilizován přes `useCallback` (setter pattern, nemění se)
- • Žádná změna UI ani business logiky — čistě interní refactor pro lepší udržovatelnost a render perf
- Perf: paralelní načítání na stránce hodnocení makléře
- • `seller/rate/[matchId]` — načtení `matches` detailu a `broker_ratings` existujícího hodnocení nyní přes `Promise.all` místo sekvenčně (oba dotazy klíčují pouze na `matchId` z URL a nezávisí na sobě)
- • Úspora ~50 ms latence na každém otevření stránky
- Fix: setTimeout cleanup v auth/profile hooks
- • `useChangePassword.ts` a `useProfileForm.ts` držely `setTimeout` bez cleanup → pokud uživatel navigoval pryč nebo zavřel dialog během 2-3s okna, React vypisoval "state update on unmounted component" warn + potenciální memory leak
- • Standardní pattern: `useRef<ReturnType<typeof setTimeout>>` + cleanup v `useEffect` (stejný pattern jako `useNotifications.ts`)
- Lokalizace Supabase auth chyb do češtiny
- • Nový helper `lib/utils/auth-errors.ts` — funkce `translateAuthError(err)` mapuje anglické chybové zprávy od Supabase Auth (leaked password, slabé heslo, invalid credentials, rate limit, duplikátní email) na uživatelsky přívětivé české texty
- • Použito v 4 místech: `useChangePassword.ts` (změna hesla v profilu), `reset-password/page.tsx` (zapomenuté heslo), `use-seller-register.ts` + `use-broker-register.ts` (registrace — detail pro password větev)
- • Hlavní dopad: při leaked password (HaveIBeenPwned) vidí uživatel "Toto heslo bylo nalezeno v úniku dat nebo je příliš jednoduché. Zvolte prosím jiné." místo anglické "Password is known to be weak and easy to guess..."
- • Login error zůstává univerzální ("Neplatný email nebo heslo") — security best practice (není žádoucí odhalit, zda email v systému existuje)
- DB optimalizace: Supabase advisors (M1-M5)
- • **M1 `security_quick_wins_m1`** — `SET search_path` na 2 funkcích (`update_support_ticket_updated_at`, `notify_admin_pending_review` SECURITY DEFINER), DROP dead-code `insert_service` policy na 3 tabulkách (`notifications`, `checkin_tokens`, `email_action_tokens`)
- • **M2 `missing_fk_indexes_m2`** — 7 indexů pro unindexed FK: `broker_ratings.moderated_by/seller_id`, `checkin_tokens.user_id`, `leads.reviewed_by`, `profiles.approved_by/suspended_by`, `support_tickets.admin_replied_by`
- • **M3 `rls_init_plan_fix_m3`** — přepis `audit_logs.admin_read_all` policy: `auth.uid()` → `(SELECT auth.uid())` pro init plan optimalizaci (eval 1× per query místo per řádek)
- • **M4 `consolidate_rls_policies_m4`** — konsolidace duplicitních permissive policies na 5 tabulkách (16 WARN findings): `broker_ratings` SELECT (3→1), `checkin_tokens`/`email_action_tokens`/`support_tickets` SELECT (2→1), `matches` UPDATE (2→1). Chování je funkčně identické, jen rychlejší.
- • **M5 `storage_bucket_listing_m5`** — DROP dead-code SELECT policies `Anyone can view photos` (property-photos) a `public_read_brand_assets` — ověřeno, že v projektu není ani jedno volání `.list()` nebo `.createSignedUrl()`. Public URL access funguje přes `bucket.public=true` flag, ne policy.
16. dubna 2026
- Bugfix: Terms re-acceptance loop
- • **Opraveno**: Po odsouhlasení aktualizovaných podmínek (broker) se stránka zobrazila znovu místo přesměrování na dashboard — chyběl `refetchProfile()` po DB updatu, `useAuthGuard` viděl stale `terms_version` a redirectoval zpět
- Reset verzí podmínek na 0.5
- • **Verze podmínek**: Změna z `2.0` na `0.5` (produkční bude `1.0`) v `terms-config.ts`
- • **DB reset**: Všichni uživatelé nastaveni na `terms_version = '0.4'` → vyžadována re-akceptace při příštím přihlášení
- • **Seller blocking redirect**: `useAuthGuard` nyní redirectuje i sellery na `/auth/accept-terms` při outdated podmínkách (dříve jen non-blocking banner, který nebyl nikde renderovaný)
11. dubna 2026
- Legal compliance: GDPR a právní dokumenty
- • **Seller consent tracking**: Přidán povinný checkbox souhlasu s podmínkami při registraci sellera + ukládání `terms_accepted_at` a `terms_version` do DB
- • **Google OAuth accept-terms**: Nová stránka `/auth/accept-terms` — OAuth selleři musí odsouhlasit podmínky po prvním přihlášení
- • **Terms re-acceptance flow**: `useAuthGuard` kontroluje verzi podmínek — broker = blocking redirect, seller = non-blocking banner (`terms-reaccept-banner.tsx`)
- • **Sdílená verze podmínek**: `lib/terms-config.ts` — single source of truth pro verze (seller + broker)
- • **Site-wide footer**: Nová sdílená komponenta `footer.tsx` s právními odkazy (Podmínky, Ochrana soukromí, Kontakt) — přidán do root layout, odstraněn duplicitní inline footer z landing page
- • **Email footer legal links**: Přidány odkazy na Podmínky a Ochranu soukromí do patičky všech emailů (`brand.ts`)
- • **Contact form privacy notice**: Informace o zpracování osobních údajů a 3-leté retenci před submit tlačítkem
- • **EXIF metadata stripping**: Fotky < 300KB nyní procházejí canvas redraw (strip GPS/EXIF dat) místo přeskakování komprese
- • **Data export (GDPR čl. 20)**: Nová karta "Export dat" v profilu s mailto odkazem pro žádost o přenositelnost údajů
- Pre-launch audit: P1 opravy
- • Přidáno `error.tsx` — uživatelsky přívětivá error boundary s Sentry + "Zkusit znovu" tlačítkem
- • Přidáno `not-found.tsx` — brand 404 stránka s odkazem na hlavní stránku
- • Opraveno: `handleDeleteLead` v admin lead detail — přidán chybějící error check na mazání nabídek
- • Nahrazeno 4x `<img>` za Next.js `<Image>` komponentu (MFA loga, admin property photos)
- • Vyčištěno 8 ESLint warnings (nepoužívané importy/proměnné z refaktoringu) — 0 warnings
- Pre-launch audit: P3 vylepšení
- • Přidáno `aria-label` na 4 icon-only tlačítka (photo-lightbox: zavřít, předchozí, další; notifications: zpět)
- • Přidáno page-level metadata na 8 veřejných stránek (kontakt, login, registrace seller/broker, registrace, podmínky, podmínky broker, ochrana údajů)
- Pre-launch audit: P2 opravy
- • Opraveno N+1 admin email notifikace v `confirm-contract` a `report-sale` — sekvenční loop nahrazen `Promise.all()` batch fetchem
- • Přidáno Sentry error tracking na fire-and-forget notifikace ve 4 API routes (`confirm-contract`, `report-sale`, `submit-rating`, `support-ticket`)
9. dubna 2026
- Refactor: auth/register/seller — rozpad na hook + sekce (konzistence s broker)
- • `page.tsx` zmenšen z 461 na 56 řádků (orchestrátor)
- • `use-seller-register.ts` (123 ř.) — hook: formData, validace, handleSubmit, redirect
- • `section-form.tsx` (83 ř.) — formulář: Google OAuth, inputs, submit
- • `section-terms-modals.tsx` (96 ř.) — Terms + Privacy modální dialogy
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
- Refactor: admin/bids list page — rozpad na hook + sekce
- • `page.tsx` zmenšen z 366 na 57 řádků
- • `use-bids.ts` (103 ř.) + `section-table.tsx` (115 ř.) + `types.ts` (38 ř.)
- Refactor: admin/users list page — rozpad na hook + sekce
- • `page.tsx` zmenšen z 366 na 69 řádků
- • `use-users.ts` (105 ř.) + `section-table.tsx` (113 ř.) + `types.ts` (34 ř.)
- Refactor: admin/leads list page — rozpad na hook + sekce
- • `page.tsx` zmenšen z 383 na 90 řádků (orchestrátor)
- • `use-leads.ts` (105 ř.) + `section-table.tsx` (116 ř.) + `types.ts` (29 ř.)
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
- Refactor: admin/ratings page — rozpad na hook + sekce
- • `page.tsx` zmenšen z 450 na 89 řádků (orchestrátor + Suspense wrapper)
- • `use-ratings.ts` (136 ř.) — hook: fetch, filter, expand, handleModerate, deep-link
- • `section-table.tsx` (148 ř.) — tabulka s expandable rows (star ratings, detail) + moderace
- • `types.ts` (50 ř.) — RatingRow, lookupLabel, CSV_COLUMNS
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
- Refactor: admin/support page — rozpad na hook + sekce + konstanty
- • `page.tsx` zmenšen z 575 na 90 řádků (orchestrátor + Suspense wrapper)
- • `use-support.ts` (183 ř.) — hook: fetch, filtry, expand, handleReply, deep-link
- • `support-constants.ts` (20 ř.) — STATUS_CONFIG, CATEGORY_CONFIG, STATUS_OPTIONS
- • `section-filters.tsx` (69 ř.) — search + status/category selects
- • `section-table.tsx` (177 ř.) — tabulka s expandable rows + reply form + pagination
- • `types.ts` (31 ř.) — SupportTicket, CSV_COLUMNS
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
- Refactor: admin/networks page — rozpad na hook + sekce + dialog
- • `page.tsx` zmenšen z 582 na 65 řádků (orchestrátor)
- • `use-networks.ts` (244 ř.) — hook: fetch, CRUD, approval workflow, slug gen
- • `section-pending.tsx` (152 ř.) — pending approvals tabulka + approve dialog
- • `section-networks-table.tsx` (72 ř.) — hlavní tabulka sítí
- • `add-network-dialog.tsx` (81 ř.) — dialog pro přidání sítě
- • `types.ts` (4 ř.) — ProfileWithCustomNetwork
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
- Refactor: admin/matches list page — rozpad na hook + sekce + konstanty
- • `page.tsx` zmenšen z 703 na 100 řádků (orchestrátor)
- • `use-matches.ts` (203 ř.) — hook: fetch, filtrování, sorting, handleStatusChange (state machine)
- • `matches-constants.ts` (29 ř.) — MATCH_STATUS_CONFIG, VALID_TRANSITIONS
- • `section-status-tabs.tsx` (52 ř.) — status filter taby s počty a ikonami
- • `section-table.tsx` (231 ř.) — tabulka s 11 sloupci, inline status select, pagination
- • `types.ts` (60 ř.) — Match, MatchStatus, CSV_COLUMNS
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
- Refactor: admin/audit page — rozpad na hook + sekce + konstanty
- • `page.tsx` zmenšen z 737 na 63 řádků (orchestrátor)
- • `use-audit-log.ts` (246 ř.) — hook: fetch, filtrování, sorting, pagination, entity cache
- • `audit-constants.ts` (147 ř.) — ACTION_LABELS, ENTITY_LABELS, ROLE_BADGE_CONFIG, METADATA_KEY_LABELS, formattery
- • `section-filters.tsx` (99 ř.) — filter bar: search, dropdowns, date range
- • `section-table.tsx` (221 ř.) — tabulka: header, expandovatelné řádky, detail, pagination, CSV export
- • `types.ts` (35 ř.) — AuditLog, EntityField, EntityCacheEntry, CSV_COLUMNS
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
- Refactor: auth/verify page — rozpad na hook + sekce
- • `page.tsx` zmenšen z 552 na 126 řádků (orchestrátor)
- • `use-verify.ts` (366 ř.) — hook: 13 stavů, 5 efektů, 8 handlerů (email, SMS, phone, redirect)
- • `section-email.tsx` (47 ř.) — email verifikace karta
- • `section-phone.tsx` (130 ř.) — phone verifikace: OTP flow + phone input (Google OAuth)
- • `types.ts` (35 ř.) — EmailSectionProps, PhoneSectionProps
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
- Refactor: bid-comparison-table — rozpad na adresář s moduly
- • `bid-comparison-table.tsx` (652 ř.) → adresář `bid-comparison-table/` se 6 soubory
- • `index.tsx` (318 ř.) — hlavní JSX komponenta (tabulka, header, body, footer)
- • `use-bid-comparison.ts` (167 ř.) — hook: state, handlers, memos (rozdíly, filtrování, eliminace)
- • `collapsible-category.tsx` (84 ř.) — sbalitelná sekce kategorií služeb
- • `service-row.tsx` (32 ř.) — řádek služby s check/cross ikonou
- • `text-cell-popover.tsx` (53 ř.) — text s popoverem pro dlouhý obsah
- • `types.ts` (15 ř.) — BidComparisonTableProps, CategoryKey, ServiceItem
- • Import path zachován (`@/components/bid-comparison-table`)
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
- Refactor: checkin-send Edge Function — rozpad na moduly
- • `index.ts` zmenšen z 461 na 68 řádků (auth + orchestrátor)
- • `helpers.ts` (86 ř.) — sdílené: NotifPrefs typ, getNotifPref, sendEmailViaBrand, createActionToken, parseMatchRelations
- • `contract-prompt.ts` (106 ř.) — Fáze A: "Podepsali jste smlouvu?" (7 dní po matchi)
- • `contract-reminder.ts` (106 ř.) — Fáze B: Připomínka + admin alert (21 dní)
- • `sale-check.ts` (150 ř.) — Fáze C+D: Sale check (42 dní) + opakovaný (3 měsíce)
- • Žádná změna chování — čistě DX refaktorizace
- Refactor: send-notification-email Edge Function — rozpad na moduly
- • `index.ts` zmenšen z 807 na 99 řádků (HTTP handler only)
- • 3 infrastrukturní moduly: `types.ts` (EmailType, EmailPayload, TemplateMap), `vocative.ts` (vokativ), `brand.ts` (SITE_URL, BRAND, header/footer/infobox/button)
- • 7 template souborů v `templates/`: `lead.ts`, `match.ts`, `sale.ts`, `broker.ts`, `checkin.ts`, `contract.ts`, `watchdog.ts`
- • Barrel `templates/index.ts` — merge všech 23 šablon do jednoho Record
- • Přidán chybějící `action_token` do `EmailPayload` typu (type-only fix)
- • Žádná změna chování ani email outputu — čistě DX refaktorizace
- Refactor: New lead wizard step 3 — rozpad na params sub-komponenty
- • `seller/new-lead/step-3-parameters.tsx` zmenšen z 901 na ~85 řádků (orchestrátor)
- • 6 sub-komponent: `params-apartment`, `params-house`, `params-commercial`, `params-common`, `params-amenities`, `params-encumbrances`
- • `CheckboxItem` helper v params-amenities, `EncumbranceGroup` helper v params-encumbrances (eliminace repetitivního JSX)
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
- Refactor: Broker lead detail — rozpad na sekce a hook
- • `broker/leads/[id]/page.tsx` zmenšen z 892 na ~50 řádků (orchestrátor)
- • 2 section komponenty: `section-lead-info` (property details + photos + map), `section-bid-panel` (formulář/status + confirm dialog)
- • Centrální hook `use-broker-lead-detail.ts` (10 state, 1 efekt, 6 handlerů)
- • Sdílené typy v `types.ts` (BrokerLead, ExistingBid, BidFormData, section Props)
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
- Refactor: Broker match detail — rozpad na sekce a hook
- • `broker/matches/[id]/page.tsx` zmenšen z 970 na ~60 řádků (orchestrátor)
- • 5 section komponent: `section-header`, `section-seller-contact`, `section-cooperation`, `section-property-details`, `section-bid`
- • Centrální hook `use-broker-match-detail.ts` (10 state, 1 efekt, 4 handlery)
- • Sdílené typy v `types.ts` (MatchDetail, section Props)
- • `ContractConfirmButton` extrahován jako reusable sub-komponenta (eliminace duplikace AlertDialog)
- • `PropertyRow` helper v section-property-details (eliminace opakovaného JSX)
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
- Refactor: Broker register page — rozpad na sekce a hook
- • `auth/register/broker/page.tsx` zmenšen z 1064 na ~113 řádků (orchestrátor)
- • 5 section komponent: `section-personal-info`, `section-company-info`, `section-network`, `section-credentials`, `section-terms`
- • Centrální hook `use-broker-register.ts` (15 state, 3 efekty, 10 handlerů)
- • Sdílené typy v `types.ts` (BrokerFormData, IcoVerificationState, section Props)
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
8. dubna 2026
- Refactor: Admin user detail — rozpad na sekce a hook
- • `admin/users/[id]/page.tsx` zmenšen z 1137 na ~130 řádků (orchestrátor)
- • 4 section komponenty: `section-header`, `section-user-info` (2 exporty), `section-broker-prefs`, `section-data-tables`
- • Centrální hook `use-admin-user-detail.tsx` (13 state, fetch, 8 handlerů)
- • Sdílené typy v `types.ts` (UserDetail, section Props)
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
- Refactor: Admin lead detail — rozpad na sekce a hook
- • `admin/leads/[id]/page.tsx` zmenšen z 1187 na ~130 řádků (orchestrátor)
- • 3 section komponenty: `lead-header-section`, `property-info-section`, `bids-table-section`
- • Centrální hook `use-admin-lead-detail.tsx` (10 state, fetch, 15 handlerů)
- • Section Props přidány do existujícího `lead-detail-types.ts`
- • Existující `property-details-section` a `property-params-section` beze změn
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
- Refactor: Seller lead detail — rozpad na sekce a hook
- • `seller/leads/[id]/page.tsx` zmenšen z 1254 na ~110 řádků (orchestrátor)
- • 6 section komponent: `section-banners`, `section-match-info`, `section-collaboration`, `section-bids`, `section-lead-detail`, `section-actions`
- • Centrální hook `use-lead-detail.ts` (18 state, fetch, 5 handlerů, derived values)
- • Sdílené typy v `types.ts` (SellerLead, section Props)
- • Deduplikovaný ContractConfirmButton (dříve 2x duplikovaný AlertDialog)
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
- Refactor: Seller lead edit — rozpad na field komponenty a hook
- • `seller/leads/[id]/edit/page.tsx` zmenšen z 1256 na ~110 řádků (orchestrátor)
- • 5 field komponent: `fields-apartment`, `fields-house`, `fields-commercial`, `fields-land-other`, `fields-shared`
- • Centrální hook `use-edit-lead.ts` (state, fetch, performUpdate, handleSubmit)
- • Sdílené typy v `types.ts` (SellerLead, EditFormData, INITIAL_EDIT_FORM, EditSectionProps)
- • 3x duplikovaný inline PENB select nahrazen existující `<EnergyRatingSelect>` komponentou
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
- Refactor: Admin match detail — rozpad na komponenty a hook
- • `admin/matches/[id]/page.tsx` zmenšen z 1493 na ~140 řádků (orchestrátor)
- • 8 section komponent: `section-timeline`, `section-property`, `section-parties`, `section-broker-offer`, `section-sale`, `section-invoice`, `section-checkin`, `section-actions`
- • Centrální hook `use-match-detail.ts` (state, fetch, 8 handlerů, email funkce)
- • Sdílené typy v `types.ts`, business konstanty v `constants.ts`
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
- Refactor: Profil stránka — rozpad na komponenty a hooky
- • `profile/page.tsx` zmenšen z 2124 na 206 řádků (orchestrátor)
- • 5 tab komponent: `tab-profile`, `tab-company`, `tab-notifications`, `tab-templates`, `tab-account`
- • 5 custom hooků: `useProfileForm`, `useIcoVerification`, `useServiceProfiles`, `useChangePassword`, `useDeleteAccount`
- • `useIcoVerification` deduplikuje dříve duplicitní kód (vlastní IČO + parent IČO)
- • Utility `notification-prefs.ts` (getPref/setPref/deriveNotifyNewLeads)
- • `CZECH_REGIONS` přesunuto do sdílených `lib/constants.ts`
- • Nový `profile/types.ts` se sdílenými typy a props interfaces
- • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
7. dubna 2026
- Feature: Email-driven match lifecycle (1-click tokeny + sale check)
- • Nový systém řídí celý lifecycle matche přes email — seller nemusí na web
- • 1-click potvrzení z emailu přes token (tabulka `email_action_tokens`, endpoint `/api/email-action`)
- • Potvrzovací stránka `/email-action` s ochranou proti náhodnému kliknutí (Card + "Ano, potvrzuji")
- • Fáze A: Contract prompt 7 dní po matchi (1-click token CTA místo odkazu na web)
- • Fáze B: Contract reminder 21 dní po matchi + admin notifikace pokud nikdo nepotvrdil
- • Fáze C: Sale check 42 dní (6 týdnů) po `contracted` — "Došlo k prodeji?"
- • Fáze D: Opakovaný sale check každé 3 měsíce
- • Nové email šablony: `contract_reminder`, `sale_check`
- • Nový notifikační typ `cooperation` (smlouva + prodej) v profilu — nahrazuje `checkin`
- • Starý check-in systém (Probíhá/Dokončeno/Problém) odstraněn — CheckinBanner ze seller/broker detailů
- • `checkin-send` kompletně přepsán na v8 (match lifecycle)
- • `send-notification-email` v21 (2 nové šablony + token CTA)
- Feature: Nový stav matche `contracted` (Spolupráce zahájena)
- • Nový stav mezi `watching` a `sale_reported` — indikuje podepsanou zprostředkovatelskou smlouvu
- • Automatický přechod: broker potvrdí podpis smlouvy → stav `watching` → `contracted`
- • Seller sám nestačí — čeká se na makléře (makléř by šel proti sobě, kdyby lhal)
- • `contracted` NENÍ prerekvizita prodeje — broker může nahlásit prodej přímo z `watching`
- • Admin matches list: nový tab "Spolupráce zahájena" s počítadlem
- • Admin match detail: nový krok v timeline mezi "Spárováno" a "Nahlášeno"
- • Watchdog (katastr) a check-in běží i ve stavu `contracted`
- • DB migrace: rozšířen CHECK constraint na `matches.status`
- • Upravené API routes: `confirm-contract`, `report-sale`, `confirm-sale-seller`
- • Deploy Edge Functions: `checkin-send` v10, `watchdog-check` v16
- Fix: Konzistentní mezery mezi kartami na seller/broker match detailu
- • Sjednoceny margin-bottom na `mb-6` u všech Card sekcí na seller lead detailu (bylo mb-4/mb-6/mb-8)
- • Sjednoceny margin-bottom na `mb-6` u všech Card sekcí na broker match detailu (bylo mb-8)
6. dubna 2026
- UX: Broker match detail — kompletní info o nemovitosti + sbalitelné sekce
- • Detail nemovitosti nyní zobrazuje všechny parametry (dispozice, stav, PENB, omezení, příslušenství, house features, rok kolaudace/rekonstrukce) — stejné info jako při dávání nabídky
- • Exteriérové fotografie odhaleny po matchi (nově přidáno k interiérovým)
- • Fotografie používají PhotoGallery komponentu s lightboxem (místo základního img gridu)
- • Sekce nabídky zobrazuje kompletní detail: služby podle kategorií s checkmarky (readonly ServiceCheckboxes), další služby, zprávu, datum odeslání
- • Opravena duplicitní přesná adresa (exact_address a street/zip/city zobrazovaly totéž)
- • Obě sekce (nemovitost + nabídka) jsou sbalovací — nemovitost defaultně rozbalená, nabídka defaultně sbalená
- • Collapsible trigger s ChevronDown animací a textem Zobrazit/Skrýt (konzistentní se seller detailem)
- • Hero banner: přidán typ nemovitosti + lokace + plocha (např. "Byt - Plzeň - Bolevec 85 m²")
- • Karta nemovitosti: popis změněn na "Podrobné informace o nemovitosti"
- • Přijaté nabídky na `/broker/bids`: karta zobrazuje přesnou adresu místo anonymní lokality (po matchi)
- • Hero banner match detailu: typ + přesná adresa (bez m², ty jsou v podrobnostech)
- • Karta nemovitosti: zjednodušený nadpis "Podrobné informace o nemovitosti" (typ + adresa je v hero)
- • Sjednoceno zarovnání collapsible CardHeaders — CardAction slot místo flex-row override + text-left fix na trigger buttons (seller i broker, 4 místa)
- • AlertDialog potvrzení pro "Potvrdit podpis smlouvy" — ochrana proti náhodnému dotyku (seller i broker, 4 místa)
- UX: Seller lead detail — zarovnání a sbalitelné nabídky
- • Zarovnání tlačítka "Ano, prodej proběhl" na úroveň inputu prodejní ceny (fix `flex items-start`)
- • Nabídky makléřů u matched leadu jsou nyní defaultně sbalené, rozbalí se kliknutím na Card trigger s textem "Zobrazit nabídky"
- • Detail poptávky u matched leadu také defaultně sbalený — trigger je přímo v CardHeader (jedna Card, ne dva oddělené boxy), u ostatních stavů vždy rozbalený
- • Sloučení sekcí smlouvy a prodeje do jedné Card (seller i broker) — Separator mezi sekcemi
- • Redesign Card "Stav spolupráce" — CardHeader s nadpisem, sekční nadpisy, info-box styl na stavových zprávách (seller i broker)
- • Vertikální layout prodeje (input + tlačítko pod sebou, konzistentně se smlouvou)
- • Sjednocení collapsible triggerů — nabídky i detail poptávky používají CardTitle + CardDescription (stejná velikost, výška)
- Feature: Potvrzení smlouvy a prodeje — obě strany
- • Seller i broker mohou nezávisle potvrdit podpis zprostředkovatelské smlouvy (nová UI sekce na detailu leadu/matche)
- • Seller může potvrdit prodej nemovitosti (paralelně k broker report-sale, volitelná cena)
- • 4 stavy smlouvy: nikdo nepotvrdil → jedna strana → obě strany → success
- • Automatický email "Podepsali jste smlouvu?" 7 dní po matchi (CRON Fáze D v checkin-send v6)
- • 4 nové email šablony: `contract_prompt`, `contract_signed_notification`, `contract_both_signed`, `seller_confirmed_sale`
- • 2 nové API endpointy: `/api/confirm-contract`, `/api/confirm-sale-seller` (Bearer JWT auth, rate limit, audit log)
- • Admin match detail: zobrazení stavu smlouvy a seller potvrzení prodeje
- • DB migrace: 5 nových sloupců v `matches` (contract_signed_by_seller_at, contract_signed_by_broker_at, seller_confirmed_sale_at, seller_confirmed_sale_price, contract_prompt_sent_at)
- Notifikace: Sjednocení check-in emailů do brand design systému
- • Check-in emaily (broker, seller, reminder, red flag) nyní používají stejný brand design jako ostatních 13 typů emailů
- • Přidány 4 nové šablony do `send-notification-email` v19: `checkin_broker`, `checkin_seller`, `checkin_reminder`, `checkin_red_flag`
- • Nový helper `checkinButtons()` — 3 CTA tlačítka (Probíhá/Dokončeno/Problém) v brand paletě
- • Refaktorován `checkin-send` v5 — emaily posílány přes `send-notification-email` (místo přímého Resend API)
- • Opravena chybějící diakritika v in-app notifikacích check-in systému ("Pripominame" → "Připomínáme")
- • Zdrojový kód `checkin-send` přidán do lokálního repo (`supabase/functions/checkin-send/index.ts`)
- UX: Sjednocení stylu placeholderů napříč webem
- • Přidána kurzíva (`italic`) pro všechny placeholdery — centrálně v Input, Textarea, Select a LocationFilter komponentách
- • Prefix "např." u 25 placeholderů, které vypadaly jako reálná data (jméno, telefon, IČO, bank. účet, cena, provize)
- • Dotčené stránky: registrace, profil, kontakt, broker nabídka/match, admin match/networks
- • Auth emaily (login, registrace, forgot-password) ponechány bez "např." — standardní UX pattern
- UX: Kontaktní údaje po matchi — email + telefon pro obě strany
- • Prodávající nyní vidí telefon a email makléře v kontaktní kartě po spárování
- • Makléř nyní vidí email prodávajícího vedle telefonu v kontaktní kartě
- • Nová RPC funkce `get_match_contact_email(match_id)` — bezpečně vrací email protistrany (SECURITY DEFINER, ověření účasti v matchi)
- • Sjednocený design kontaktních karet (seller i broker): bg-[#f5f0ff], label-above-value grid, klikací tel:/mailto: linky
- Fix: Chybějící diakritika v in-app notifikacích
- • Opravena chybějící česká diakritika v 10 textových řetězcích notifikací (SQL triggery + Edge Function)
- • Postižené notifikace: bids_ready, new_lead, match (seller i broker), lead_approved (auto-approve)
- • Nová migrace `20260406_fix_notification_diacritics.sql` — 3x `CREATE OR REPLACE FUNCTION`
- • Edge Function `auto-approve-leads` v7 — opraveny 2 textové řetězce
- • Retroaktivně opraveny existující záznamy v tabulce `notifications`
5. dubna 2026
- Security: HTML injection fix + rate limiting
- • Přidána `escapeHtml()` utilita — všechny uživatelské vstupy v support ticket emailech jsou nyní escapovány
- • Přidán rate limit na `/api/staging-auth` (5/min, brute-force ochrana)
- • Opravena špatná rate limit konstanta na `/api/send-notification` (reverseGeocode → sendNotification)
- Fix: SQL trigger funkce — source control + deprecated GUC
- • 4 notification trigger funkce (notify_brokers_new_lead, notify_new_bid, notify_match_created, notify_admin_pending_review) + 6 triggerů nyní trackováno v migracích
- • `notify_new_bid` a `notify_match_created` opraveny: odstraněn deprecated `current_setting()`, vault-only pattern
- Refactor: Kódová konzistence
- • Extrahována duplicitní `evaluateCheckinResponses()` do sdíleného `lib/checkin-utils.ts` s proper SupabaseClient typem
- • 16x `alert()` nahrazeno `toast.error()` ve 3 admin stránkách (users, leads, networks)
- • 14x `toLocaleDateString("cs-CZ")` nahrazeno `formatDate()` utilitou v 9 souborech
- UX: Placeholdery ve formuláři poptávky
- • Číselné placeholdery ve Step 3 (Parametry) dostaly prefix "např." — plocha, patro, celkem pater, počet místností
- • Předchozí placeholdery ("3", "5", "75") vypadaly jako vyplněná data
4. dubna 2026
- Feature: Schvalování registrace makléřů
- • Nové sloupce v `profiles`: `approved_at`, `approved_by`, `registration_rejection_reason`
- • CHECK constraint rozšířen o `pending_approval` a `rejected`
- • `handle_new_user()` — brokeři dostanou `status = 'pending_approval'`
- • Trigger `notify_admin_pending_broker()` — in-app notifikace adminům
- • SQL funkce `cleanup_rejected_brokers()` + CRON denně 3:00 UTC
- • Nová stránka `/auth/pending-approval` — čekací/zamítnutá stránka s pollingem (30s)
- • `useAuthGuard` — KONTROLA 4.5: redirect pending/rejected brokerů
- • `/auth/verify` — fix double-redirect pro pending brokery
- • `/auth/register/broker` — info text "Registrace podléhá schválení"
- • `/admin/users/[id]` — tlačítka Schválit/Zamítnout, info karta, dual-write notifikace
- • `/admin/users` — filtr "Ke schválení" + "Zamítnutí", status badge
- • Audit log: `approve_broker`, `reject_broker` akce
- • `send-notification-email` v18 — nové šablony `broker_approved`, `broker_rejected`
- • `/api/send-notification` whitelist rozšířen
- • `supabase/migrations/20260404120000_broker_pending_approval.sql`
- • `supabase/functions/send-notification-email/index.ts`
- • `frontend/src/hooks/useAuthGuard.ts`
- • `frontend/src/app/auth/pending-approval/page.tsx` (NOVÝ)
- • `frontend/src/app/auth/verify/page.tsx`
- • `frontend/src/app/auth/register/broker/page.tsx`
- • `frontend/src/app/admin/users/[id]/page.tsx`
- • `frontend/src/app/admin/users/page.tsx`
- • `frontend/src/app/admin/audit/page.tsx`
- • `frontend/src/app/api/send-notification/route.ts`
- • `frontend/src/lib/supabase/types.ts`
- Fix: Audit log filtry — responsivní layout při středních šířkách
- • `frontend/src/app/admin/audit/page.tsx` — `lg:grid-cols-5` → `xl:grid-cols-5`, search span na `md:col-span-2`
- Fix: Audit log — detail pod řádkem + vyhledávání podle e-mailu
- • Detail se nyní renderuje jako inline řádek přímo pod kliknutým záznamem
- • Přidáno vyhledávání podle e-mailu (paralelní fetch přes `get_users_with_email` RPC)
- • E-mail zobrazen pod jménem ve sloupci Uživatel
- • CSV export rozšířen o sloupec E-mail
- • `frontend/src/app/admin/audit/page.tsx` — Fragment pattern pro inline detail, email fetch + zobrazení + search
- Enhance: Audit log — obohacená metadata pro právní důkazy
- • `create_lead` — přidáno: ulice, PSČ, dispozice, kraj, okres, entity ID
- • `create_match` — přidáno: město, typ, ulice, jméno makléře, ID makléře, cena nabídky, provize
- • `extend_lead` — přidáno: město, typ nemovitosti
- • `submit_bid` — přidáno: město a typ poptávky
- • `submit_rating` — přidáno: jméno makléře
- • `approve_lead` / `reject_lead` — přidáno: město, typ, jméno prodávajícího
- • 7 match akcí (confirm_sale, issue_invoice, record_payment, cancel_match, revert_to_watching, approve/reject_sale_report) — přidáno: jméno makléře/prodávajícího, město, ceny, faktury
- • `change_match_status` — přidáno: jméno makléře, město
- • 3 user akce (change_role, suspend, unsuspend) — přidáno: jméno a email cílového uživatele
- • `moderate_rating` — přidáno: jména makléře/prodávajícího, hodnocení
- • České popisky metadata klíčů (property_type → "Typ nemovitosti", broker_name → "Makléř" atd.)
- • Překlad hodnot (typ nemovitosti, ceny formátovány jako Kč)
- • On-demand entity fetch při rozbalení řádku — kontext z DB pro staré záznamy (lead, match, bid, rating, support_ticket)
- • Cache pro zamezení opakovaných dotazů
- • `frontend/src/app/admin/audit/page.tsx` — METADATA_KEY_LABELS, entity cache + fetch, EntityContext sekce
- • `frontend/src/app/seller/new-lead/page.tsx`, `seller/leads/[id]/page.tsx`, `seller/rate/[matchId]/page.tsx`
- • `frontend/src/app/broker/leads/[id]/page.tsx`
- • `frontend/src/app/api/extend-lead/route.ts`, `api/report-sale/route.ts`, `api/submit-rating/route.ts`
- • `frontend/src/app/admin/leads/[id]/page.tsx`, `admin/matches/[id]/page.tsx`, `admin/matches/page.tsx`
- • `frontend/src/app/admin/users/[id]/page.tsx`, `admin/ratings/page.tsx`
21. března 2026
- Fix: Zkrácený nadpis ticketu zůstával oříznutý i po rozbalení (mobil)
- • `frontend/src/app/support/page.tsx` — podmíněné `truncate` jen pro zavřený stav, rozbalený zobrazí celý nadpis (`whitespace-normal break-words`)
- Fix: Email notifikace z admin panelu nefungovaly
- • `lead_approved` email sellerovi při schválení poptávky
- • `lead_rejected` email sellerovi při zamítnutí poptávky
- • `sale_confirmed` email makléři při potvrzení prodeje
- • `rating_request` email sellerovi s žádostí o hodnocení
- • `frontend/src/app/api/send-notification/route.ts` — nová server-side proxy (auth + admin role check + service_role forwarding)
- • `frontend/src/app/admin/leads/[id]/page.tsx` — přesměrování na `/api/send-notification`
- • `frontend/src/app/admin/matches/[id]/page.tsx` — přesměrování na `/api/send-notification`
- Fix: Přetékání předmětu ticketu na /support (mobil)
- • `frontend/src/app/support/page.tsx` — přidáno `overflow-hidden` na flex wrapper v `CardHeader`
19. března 2026
- Chore: GDPR compliance — Sentry sendDefaultPii + Privacy Policy Session Replay
- • `frontend/sentry.client.config.ts` — přidáno explicitní `sendDefaultPii: false` (potvrzuje default, GDPR best practice)
- • `frontend/src/app/privacy/page.tsx` — sekce 3.14 doplněna o informaci o Sentry Session Replay (automatické maskování textu a vstupů)
- Fix: Aktualizace právních dokumentů v registračních modálech na v2.0
- • Broker: `TERMS_VERSION` 1.0 → 2.0, `EXPLICIT_POINTS` sladěny s čl. 4.3/5/9/14 Rámcové smlouvy v2.0
- • Broker: Modál smlouvy — odstraněn banner "pracovní verze", nahrazen stručným shrnutím + odkaz na `/terms/broker`
- • Broker: Modál GDPR — aktualizován na v2.0 shrnutí + odkaz na `/privacy`
- • Seller: Modál podmínek — aktualizován na v2.0 shrnutí + odkaz na `/terms`
- • Seller: Modál GDPR — aktualizován na v2.0 shrnutí + odkaz na `/privacy`
- • `PRAVNIK-BRIEF.md` — aktualizovány reference na verzi 2.0
- Chore: Odstranění mrtvého kódu
- • `frontend/src/components/lead-location-map.tsx` — odstraněn nepoužívaný prop `locationArea`
- • `frontend/src/app/broker/leads/[id]/page.tsx` — odstraněno předávání `locationArea`
- • `frontend/src/components/service-checkboxes.tsx` — odstraněn mrtvý prop `compact`, zjednodušen className (obě větve ternary byly identické)
- • `frontend/src/app/globals.css` — smazáno 9 nepoužívaných CSS utility tříd (`border-brand-gradient`, `shadow-brand-glow`, `border-l-brand`, `border-l-brand-dark`, `shadow-brand-sm/md/lg/xl`, `hover-lift`, `active-press`)
- • `frontend/src/app/profile/page.tsx` — odstraněn deprecated `full_name` zápis
- • `frontend/src/app/auth/register/seller/page.tsx` — odstraněn deprecated `full_name` zápis
- • `frontend/src/app/auth/register/broker/page.tsx` — odstraněn deprecated `full_name` zápis
18. března 2026
- Fix: Horizontální overflow na stránce /support (mobilní verze)
- • `frontend/src/app/support/page.tsx` — header: responsive stacking (`flex-col` → `sm:flex-row`), ticket karta: badges wrap pod titulek na mobilu
12. března 2026
- Feat: České skloňování jmen (vokativ) ve všech emailech
- • `supabase/functions/send-notification-email/index.ts` — v17: přidána `toVocative()` funkce, nové pole `to_first_name`, handler převádí greeting na vokativ
- • `frontend/src/app/admin/leads/[id]/page.tsx` — 2× přidáno `to_first_name` (lead_approved, lead_rejected)
- • `frontend/src/app/admin/matches/[id]/page.tsx` — 2× přidáno `to_first_name` (rating_request, sale_confirmed)
- • `supabase/functions/auto-approve-leads/index.ts` — přidáno `to_first_name` (lead_approved)
- • `frontend/src/app/api/report-sale/route.ts` — přidáno `to_first_name` (sale_reported)
- • `frontend/src/app/api/support-ticket/route.ts` — import `toVocative`, oslovení ve vokativu
- • `frontend/src/app/api/support-ticket/reply/route.ts` — import `toVocative`, oslovení ve vokativu
- Fix: Validace čísla jednotky (unit_number) — pouze číslice
- • `frontend/src/components/unit-number-input.tsx` — filtr ne-číslic na onChange, `inputMode="numeric"`, aktualizované hinty
- • `frontend/src/app/api/cadastral-lookup/route.ts` — API-level strip ne-číslic před parseInt (safety net)
- • `frontend/src/app/admin/leads/[id]/page.tsx` — stejná validace v admin editaci, opraven placeholder
- Fix: PriceInput na všech místech zadávání ceny
- • `frontend/src/app/broker/matches/[id]/page.tsx` — PriceInput pro reportPrice
- • `frontend/src/app/admin/matches/[id]/page.tsx` — PriceInput pro salePrice
10. března 2026
- Feature: Edge Function `delete-account` — soft delete uživatelského účtu
- • Nová Edge Function `delete-account/index.ts` — soft delete (anonymizace profilu + ban ~100 let)
- • Dva režimy: self-service (uživatel z profilu s ověřením hesla) a admin (z admin panelu bez hesla)
- • Broker flow: blokace při aktivních matchích (409 ACTIVE_MATCHES), reject pending bidů, smazání service profiles
- • Seller flow: cancel aktivních leadů + watching matchů, reject pending bidů na těchto leadech
- • Audit snapshot do `account_deletion_audit` před anonymizací (email, jméno, IČO, statistiky matchů)
- • Auth anonymizace: email → `deleted_{id}@deleted.spravnymakler.cz`, ban 876000h, metadata vyčištěna
- • Admin delete v `admin/users/[id]` přepsán z hard-delete na soft-delete přes Edge Function
9. března 2026
- Fix: Validace profilu blokuje uložení notifikací
- • Přidán řízený `activeTab` state pro `<Tabs>` komponentu
- • Validace IČO, office address a network membership podmíněny `activeTab === "company" || "profile"`
- • Tab Notifikace nyní ukládá bez blokování nesouvisejícími validacemi
5. března 2026
- Fix: CRON joby failují — GUC parametr odstraněn ze Supabase
- • CRON joby přeregistrovány — čtou klíč z `vault.decrypted_secrets` místo `current_setting()`
- • Vault secret aktualizován na nový `sb_secret_` formát
- • 2 zaseknuté leady (Chrást, Plzeň) auto-approved po opravě
- • `supabase/migrations/20260305080000_fix_cron_vault_auth.sql` — nová migrace
- • `supabase/migrations/20260124150000_setup_watchdog_cron.sql` — komentář o obsolete GUC
- • `CLAUDE.md` — aktualizovaná sekce CRON auth
27. února 2026
- Fix: Notifikace — deep-link s auto-expand
- • `new_rating` → `/admin/ratings?rating_id=...` (admin vidí konkrétní hodnocení)
- • `support_ticket` → `/admin/support?ticket_id=...` (admin vidí konkrétní ticket)
- • `support_reply` → `/support?ticket_id=...` (seller vidí odpověď)
- • `frontend/src/app/api/submit-rating/route.ts` — INSERT vrací ID, deep-link href
- • `frontend/src/app/api/support-ticket/route.ts` — deep-link href s ticket_id
- • `frontend/src/app/api/support-ticket/reply/route.ts` — deep-link href s ticket_id
- • `frontend/src/app/admin/ratings/page.tsx` — useSearchParams + auto-expand + scrollIntoView
- • `frontend/src/app/admin/support/page.tsx` — useSearchParams + auto-expand + scrollIntoView
- • `frontend/src/app/support/page.tsx` — useSearchParams + auto-expand + scrollIntoView
- Fix: Notifikace — revert "Zobrazit" tlačítka + fix rejection směrování
- • `/notifications` stránka: revert na klik=navigace (klik na notifikaci = markAsRead + přechod na detail), odstraněno redundantní tlačítko "Zobrazit"
- • Rejection notifikace ("vrácena k přepracování") nyní směřuje na `/seller/leads/[id]` (detail s bannerem + důvodem) místo `/seller/leads/[id]/edit` (edit stránka bez důvodu)
- • `frontend/src/app/notifications/page.tsx` — revert klik=navigace, odebrán "Zobrazit" blok
- • `frontend/src/app/admin/leads/[id]/page.tsx` — rejection href na detail místo edit
- UI: Notifikace — plný text + mobile responsivita
- • `/notifications` stránka: odstraněn `line-clamp-2` → celý body text (důvod zamítnutí apod.) je viditelný
- • Popover zvoneček: responsive šířka (`w-[calc(100vw-2rem)] sm:w-80`) — na mobilu zabere viewport
- • Header: badge zkrácen na číslo, "Přečíst vše" na mobilu jen ikona
- • `frontend/src/components/notification-bell.tsx` — responsive popover šířka
- • `frontend/src/app/notifications/page.tsx` — plný body text, responsive header
- Fix: Chybějící emaily pro sellera (lead approval + support ticket reply)
- • Přidány CORS headers do `send-notification-email` Edge Function (OPTIONS handler + `Access-Control-Allow-Origin/Headers` na všech response)
- • Přidán `RESEND_API_KEY` do Coolify env vars + `.env.local`
- • Vyměněn Resend API klíč ve všech prostředích (Supabase secrets, Coolify, lokální)
- • Přidán response status logging do support-ticket route (`.then()` + `.catch()` místo jen `.catch()`)
- • `supabase/functions/send-notification-email/index.ts` — CORS headers + OPTIONS handler (deploy v29)
- • `frontend/src/app/api/support-ticket/reply/route.ts` — Resend response logging
- • `frontend/src/app/api/support-ticket/route.ts` — Resend response logging
- • `frontend/.env.example` — přidán `RESEND_API_KEY` placeholder
- Fix: False positive v detekci duplicitních leadů (F19)
- • Kontrola duplicit porovnávala pouze `parcel_number` + `unit_number`, ale ignorovala `property_type`
- • Byt a garáž na stejné parcele jsou různé nemovitosti, ale systém je blokoval jako duplicitu
- • Přidán `.eq("property_type", formData.propertyType)` do duplikátní kontroly
- Fix: CRON auth — auto-approve-leads, checkin-send, watchdog-check 401 Unauthorized
- • `auto-approve-leads` nasazena s `verify_jwt: true` (Supabase proxy odmítne CRON request před spuštěním kódu)
- • CRON joby 4 (checkin-send) a 5 (auto-approve-leads) používaly neexistující GUC `app.settings.service_role_key` → NULL → Authorization header se neposlal
- • `watchdog-check` neměl žádný auth check ani Authorization header v CRONu — veřejně přístupný
- • Re-deploy `auto-approve-leads` s `verify_jwt: false` + explicitní auth check (service_role Bearer token)
- • Re-deploy `watchdog-check` s auth checkem (předtím bez jakékoli ochrany)
- • Oprava CRON jobů 2, 4, 5: `app.settings.service_role_key` → `supabase.service_role_key`
- • `supabase/functions/auto-approve-leads/index.ts` — auth check přidán
- • `supabase/functions/watchdog-check/index.ts` — auth check přidán
- • SQL: `cron.alter_job(2)`, `cron.alter_job(4)`, `cron.alter_job(5)` (Supabase MCP)
26. února 2026
- Fix: Admin notifikace při resubmitu leadu + Confirmation dialog
- • Admin nedostával notifikaci když seller resubmitnul zamítnutý lead (trigger byl AFTER INSERT only)
- • Nový UPDATE trigger `on_lead_resubmit_notify_admin` (rejected → pending_review)
- • Funkce `notify_admin_pending_review()` rozlišuje INSERT/UPDATE přes `TG_OP` (jiný title + `is_resubmit` metadata)
- • Confirmation dialog před resubmitem zamítnutého leadu (`LeadResubmitConfirmDialog`)
- • Shrnutí klíčových údajů (typ, adresa, plocha, dispozice, stav, PENB, omezení, fotky)
- • Čestné prohlášení o neexkluzivitě (povinný checkbox)
- • Tlačítko "Zkontrolovat a odeslat" místo "Uložit změny" pro rejected leady
- • Běžná editace pending_review leadu funguje přímo bez dialogu (bez regrese)
- • SQL migrace: `notify_admin_on_lead_resubmit` (Supabase MCP)
- • `components/lead-resubmit-confirm-dialog.tsx` — **NOVÝ**
- • `seller/leads/[id]/edit/page.tsx` — dialog integrace, split handleSubmit/performUpdate
- Brand-Consistent Email Templates (v16)
- • Gradient top border (4px, 3 barvy: `#220860→#6039f6→#AF96FC`)
- • Logo header (nové S-shape brand logo, 150×45px, Supabase Storage CDN)
- • Tmavě fialový footer (`#220860` bg, `#AF96FC` nadpisy, právní údaje OneManDev s.r.o.)
- • 3-barevný CTA gradient + solid `#6039f6` fallback pro Outlook
- • Font stack rozšířen: Geist + system fallbacks (místo jen Arial)
- • Zelená (`#22c55e`) → fialová (`#6039f6`) pro success stavy
- • Ambrová (`#f59e0b`) → lavender (`#AF96FC`) pro attention stavy
- • Oranžová (`#f97316`) → tmavě fialová (`#220860`) pro action stavy
- • Šedý plaintext footer → dark purple branded footer
- • 2-barevný CTA gradient → 3-barevný (konzistentní s webem)
- • `send-notification-email/index.ts` — v16: BRAND konstanty, 4 helper funkce, redesign 11 šablon
- • `lib/email-wrapper.ts` — **NOVÝ**: sdílený brand header/footer pro Next.js routes
- • `api/support-ticket/route.ts` — redesign 2 inline emailů
- • `api/support-ticket/reply/route.ts` — redesign 1 inline emailu
- Audit & Fix: Konzistence notifikačního systému (6 oprav)
- • Chybějící email šablony `sale_confirmed`, `sale_reported`, `rating_request` v Edge Function — emaily tiše selhávaly s 400 (→ v15, deployed)
- • `submit-rating` posílal notifikaci sellerovi místo adminům (`p_user_id: user.id` bug) — opraveno na loop přes adminy, typ změněn na `new_rating`
- • Duplikátní match notifikace — `create_match()` + trigger `notify_match_created()` = 2 in-app pro každého — odebrán `PERFORM create_notification()` z `create_match()` (SQL migrace)
- • `watchdog-check` v5 — přímý Resend nahrazen Edge Function `plomba_detected`, hardcoded `ADMIN_EMAIL` → dynamický dotaz na adminy, přidána in-app notifikace
- • `support-ticket` — přidána in-app notifikace pro adminy (`support_ticket` typ)
- • Tiché `.catch(() => {})` v admin leads → nahrazeny `console.error()` logováním
- • `send-notification-email/index.ts` — 3 nové šablony (v15)
- • `watchdog-check/index.ts` — Edge Function + in-app + dynamičtí admini (v5)
- • `api/submit-rating/route.ts` — fix admin notifikace
- • `api/support-ticket/route.ts` — in-app pro adminy
- • `admin/leads/[id]/page.tsx` — fix tiché .catch
- • `lib/constants.ts` — nové notif. typy `new_rating`, `support_ticket`
- • SQL migrace `create_match()` — odebrány duplikátní notifikace
- Fix: Chybějící notifikace sellerovi při schválení/zamítnutí leadu
- • Email při zamítnutí volal neexistující route `/api/send-notification` → opraveno na Edge Function `send-notification-email`
- • In-app notifikace při zamítnutí používala neexistující parametr `p_data` → opraveno na `p_title`/`p_body`/`p_href`/`p_metadata`
- • Seller email se nenačítal (je v `auth.users`, ne v `profiles`) → doplněn RPC `get_user_email_by_id`
- • Dual-write notifikace sellerovi při schválení leadu (admin i auto-approve)
- • Email šablona `lead_approved` v Edge Function v14
- • Auto-approve CRON (v2) odesílá seller notifikace
- • Nový typ `lead_review` v notifikačních preferencích + UI řádek "Schválení poptávky" v profilu sellera
- • `admin/leads/[id]/page.tsx` — fetch email + prefs, fix reject, přidán approve notif.
- • `admin/leads/[id]/lead-detail-types.ts` — `notification_preferences` v seller typu
- • `send-notification-email/index.ts` — šablona `lead_approved` (v14)
- • `auto-approve-leads/index.ts` — seller dual-write notifikace (v2)
- • `lib/supabase/types.ts` — `lead_review` v `NotificationPreferences`
- • `profile/page.tsx` — UI řádek pro `lead_review` (seller sekce)
25. února 2026
- UI: Vizuální odlišení přečtených vs nepřečtených notifikací
- • `notification-bell.tsx` — tečka v popoveru: barevná (nepřečtená) → šedá (přečtená)
- • `notifications/page.tsx` — tečka na stránce notifikací: stejná logika
- Feature: Admin Lead Detail — property-type-aware zobrazení + editace
- • `PropertyDetailsSection` — editace přímých DB sloupců podle property_type (disposition/ownership pro byt, house_category pro dům, land_category pro pozemek atd.)
- • `PropertyParamsSection` — editace params JSONB: rozměry, PENB, stáří/rekonstrukce, omezení (radio + checkboxy), příslušenství (9 ks byt, 8 ks dům, 7 ks komerční), vlastnosti domu (6 ks)
- • `lead-detail-types.ts` — sdílený `LeadDetail` interface + `PROPERTY_FIELD_CONFIG` centrální mapa
- • Odstraněn JSON dump "Další parametry" (nahrazen strukturovaným zobrazením)
- • Přidány lookup mapy v `constants.ts`: `AMENITY_LABELS`, `HOUSE_CHARACTERISTIC_LABELS`, `HOUSE_SIZE_LABELS`
- • Fix: amber/orange barvy v admin lead detail (`getStatusBadge`, reject button/dialog) nahrazeny brand paletou
- UI: Design konzistence — pending_review/rejected barvy sladěny s brand paletou
- • `LEAD_STATUS_CONFIG` a `SELLER_LEAD_STATUS_CONFIG` — pending_review: `bg-[#f5f0ff] text-[#6039f6]`, rejected: `bg-[#ede5ff] text-[#220860]`
- • `NOTIFICATION_CONFIG` — ikony `lead_pending_review` (#6039f6) a `lead_rejected` (#220860) místo amber/orange
- • Seller lead detail — pending/rejected bannery v on-brand barvách
- • Admin leads — badge barvy + pending alert banner v lavender tónech
- • Odstraněny všechny amber/orange třídy z kontextu lead statusů
- Fix: Lead INSERT selhal kvůli trigger type mismatch
- • DB migrace: opravena funkce `notify_admin_pending_review()` — správné volání `create_notification()` s 6 argumenty (title, body, href, metadata)
- • Nový helper `getErrorMessage()` (`lib/utils/error.ts`) — bezpečně extrahuje `.message` z `Error`, `PostgrestError` i stringů
- • Nahrazeno ve 12 souborech (17 výskytů `err instanceof Error ? err.message : ...`)
- • Přidán `Sentry.captureException()` pro lead submit
24. února 2026
- Feature: Admin Review — schvalování poptávek (F22)
- • `pending_review` — čeká na schválení adminem (seller může editovat)
- • `rejected` — vráceno k přepracování s důvodem (seller edituje a resubmitne)
- • DB migrace: CHECK constraint, sloupce `review_deadline`, `reviewed_by`, `reviewed_at`, `rejection_reason`
- • UPDATE trigger `on_lead_approved_notify_brokers` — automatická notifikace brokerů při schválení
- • INSERT trigger `on_lead_pending_review_notify_admin` — email + in-app adminu
- • Edge Function `auto-approve-leads` v1 (CRON každou hodinu) — auto-schválení po 24h
- • Edge Function `send-notification-email` v21 — 2 nové typy: `lead_pending_review`, `lead_rejected`
- • Seller: lead se vytváří jako `pending_review` s `expires_at: null`, redirect na detail
- • Seller detail: informační bannery (pending/rejected) s důvodem a tlačítkem na editaci
- • Seller edit: guard pro pending_review/rejected, resubmit logika (rejected → pending_review)
- • Seller leads: nový tab "Ke schválení" (pending + rejected leady)
- • Admin leads: amber alert banner s počtem ke schválení, filtr pending_review/rejected
- • Admin detail: zelené tlačítko "Schválit" + oranžové "Vrátit k přepracování" (AlertDialog s důvodem)
- • Status badge: ikony Clock (pending) a RotateCcw (rejected)
- • Audit log: akce `approve_lead`, `reject_lead`, `resubmit_lead`
- • Constants: LEAD_STATUS_CONFIG, SELLER_LEAD_STATUS_CONFIG, NOTIFICATION_CONFIG rozšířeny
- • TypeScript typy regenerovány z Supabase
- • `expires_at = NULL` při vytvoření, nastaví se na NOW+30d při schválení
- • `reviewed_by = NULL` = auto-approved (CRON), UUID = admin akce
- • Brokeři nevidí pending/rejected leady (RLS + view `leads_for_brokers` WHERE status='active')
23. února 2026
- UI: Gradient border přetéká přes zaoblené rohy karet
- • Gradient top border (`absolute top-0 h-1`) přetékal přes zaoblené rohy Card na stránkách support, contact, admin/support
- • Oprava: `overflow-hidden` na kartách bez dropdownů, `rounded-t-2xl` workaround na kartách s Select dropdowny
- Fix: Report-sale a checkin-respond-web API vrací 401 "Nepřihlášen"
- • API routes `/api/report-sale` a `/api/checkin-respond-web` používaly cookie-based auth (`@/lib/supabase/server`), ale session je v localStorage → vždy 401
- • Oprava: Bearer JWT pattern (stejně jako `submit-rating`) — frontend posílá `Authorization: Bearer <token>`, API ho ověřuje přes `@supabase/supabase-js`
- • Dotčené soubory: `api/report-sale/route.ts`, `broker/matches/[id]/page.tsx`, `api/checkin-respond-web/route.ts`, `components/checkin-banner.tsx`
- Feature: Rok kolaudace a rekonstrukce v parametrech leadu
- • **Rok kolaudace** (`year_built`) — rok první kolaudace/dokončení stavby (1800–aktuální rok)
- • **Rok poslední rekonstrukce** (`year_reconstructed`) — volitelné
- • **Rozsah rekonstrukce** (`reconstruction_scope`) — celková/částečná, zobrazí se jen pokud je vyplněn rok rekonstrukce
- • Data uložena v `leads.params` JSONB (žádná DB migrace)
- • Zobrazení: wizard krok 3 + shrnutí, seller/broker/admin detail, seller edit
- • Nezobrazuje se pro pozemky a ostatní typy
- UI: Vyčištění Card hover efektu
- • Odebrán globální hover efekt z Card komponenty (`hover:shadow-xl hover:border-[#AF96FC]/50 hover:-translate-y-0.5`) — aplikoval se na všechny karty včetně formulářů a tabulek
- • Odstraněn workaround `hover:shadow-none hover:-translate-y-0` z `empty-state.tsx`
- • Klikatelné karty (lead cards, admin stat cards) mají vlastní hover efekty
- Legal: Cross-reference audit právních dokumentů — 28 oprav
- • **A1**: Lhůta odvolání sjednocena na 14 dní (VOP mělo 15, RS 14)
- • **A2**: Definice Prodávajícího ve VOP rozšířena na FO/PO (RS už mělo oboje)
- • **A3**: Fikce doručení sjednocena na 3. pracovní den + dual-channel (VOP mělo "okamžitě")
- • **B1**: Google OAuth přidán do Privacy Policy (tabulka zpracovatelů + účel + přenos)
- • **B2**: Mapy.cz/Seznam.cz přidán do Privacy Policy
- • Mediační krok před soudem (čl. 19.2), Force Majeure (čl. 16.4)
- • Lhůta kontaktování po Matchi 5 prac. dní (čl. 7.1.a), SLA odvolání 30 dní (čl. 12.2.c)
- • Cross-property provize zúžena (čl. 6.5), přechodové pravidlo pro bidy (čl. 17.3)
- • Cenový semafor — "vysoká/běžná/nízká cena" místo technických kódů (čl. 2.8, 10.1)
- • Právo makléře na odpověď na hodnocení (čl. 11.4), progresivní sankce 150 % (čl. 14.5)
- • Příklady pro okamžitou suspenzi (čl. 12.3), postoupení práv Provozovatele (čl. 19.8)
- • Sjednocení lhůty, definic, fikce doručení (A1-A3), aktivní Match upřesněn (A4)
- • Právo makléře na odpověď na hodnocení (čl. 9.7)
- • 3 noví zpracovatelé (Google, Mapy.cz, support tickets) v sekcích 3.15-3.17 + tabulce 4.1
- • Balancing testy u 6 oprávněných zájmů (3.3, 3.7, 3.9, 3.10, 3.13, 3.14)
- • DPIA zmínka u Watchdog (3.7), souhrnná tabulka doby uchování (8.5)
- • Notifikační preference pro sellery (2.1)
- Legal: Komplexní revize právních dokumentů v2.0 (~40 změn)
- • Nové definice: 2.13 Realizovaný obchod (vč. nepřímých převodů přes s.r.o.), 2.14 Skutečná prodejní cena (vč. vedlejších plnění), 2.15 Propojená osoba (5 kategorií)
- • Registrace: souhlas→akceptace (GDPR konzistence), Propojené osoby v multi-account detekci
- • Provize: 5.3 formy odměny (sníží/promine/jiná forma), 5.9 sporná prodejní cena, 5.10 námitka fakturace
- • Vznik nároku: 6.5 provize z dalších nemovitostí (24 měsíců), 6.6 notifikace plomby + 7denní lhůta, 6.7 nepřímý převod = obchod, 6.8 lhůta 30 dní na fakturu
- • Anti-bypass: 8.1.g zákaz identifikace adresy z anonymizovaných údajů, 8.2 temporal regime pro g), 8.5 trojnásobek→pětinásobek s odůvodněním, 8.6 Propojená osoba dle 2.15
- • Monitoring: 9.2 automatické +12 měsíců při aktivní spolupráci, 9.4 7denní lhůta + neodpovězení = porušení
- • Sankce: 2 nové řádky (neodpovězení check-in 10k, nadhodnocení 25k), obcházení 3x→5x
- • Změny smlouvy: 17.4 ochrana aktivních matchů
- • Závěrečná: 19.5 doručovací fikce 3. pracovní den + dual-channel, 19.8 promlčecí lhůta 10 let
- • Nové definice: Úspěšný prodej, Aktivní Match, Rezervní nabídka, Opakované porušení
- • Prodávající: odpovědnost za škodu, plná moc, 7denní check-in, re-posting, pre-existing relationships
- • Makléři: zákaz využití pro kupující, self-dealing prohibition
- • Match: 14denní fallback pro neúplné sloty, 7denní výběr, match cancellation při neodpovědi 14 dní
- • Hodnocení: i po cancelled spolupráci, právo makléře na odpověď
- • Odpovědnost: oddělené capy (broker provize-based, seller 50k CZK)
- • Ukončení: 14 dní (ne 15), monitoring přežívá smazání
- • Doručení: 3. pracovní den + dual-channel
- • Data: IP adresa + user-agent, support tickety, retenční politika fotografií
- • Účely: 3 nové (support tickets, Mapy.cz geokódování, Google OAuth)
- • Balancing testy pro 4 oprávněné zájmy
- • Zpracovatelé: oprava Supabase sídla (USA, data EU Frankfurt), přidáni Seznam.cz/Mapy.cz a Google
- • Přeshraniční přenosy: Supabase a Google přidáni (SCCs + DPF)
- • Práva: čl. 22 GDPR (automatizované rozhodování)
- • Smazání účtu: rozšířená retenční politika + deleted_profiles popis
- • Cookies: localStorage zmínka
- • `LEGAL-TERMS-BROKER.md`, `LEGAL-TERMS.md`, `LEGAL-PRIVACY.md`
- • `frontend/src/app/terms/broker/page.tsx`
- • `PRAVNIK-BRIEF.md`, `ROADMAP.md`
19. února 2026
- Fix: Shrnutí nového leadu — chybějící adresa a fotografie
- • Adresa se nyní zobrazuje kompletní (ulice, město, PSČ), oblast zvlášť pokud se liší
- • Přidány náhledy nahraných fotek (64×64px thumbnaily) s celkovým počtem
- • Automaticky získaná data (parcela, LV, k.ú.) se záměrně nezobrazují — seller je nepotřebuje
- • `app/seller/new-lead/step-5-summary.tsx` — plná adresa, foto náhledy
- Fix: Prodloužení leadu — výpočet expirace + UX omezení
- • `app/seller/leads/[id]/page.tsx` — oprava výpočtu, disabled logika, toast
- • `app/api/extend-lead/route.ts` — konzistentní oprava výpočtu
- Fix: Prodloužení leadu — auth mismatch (401 "Nepřihlášen")
- • `app/seller/leads/[id]/page.tsx` — client-side extend s validací, RLS, audit log, analytics
- Feat: Responsive Navbar — hamburger menu + user dropdown
- • `components/ui/sheet.tsx` — shadcn Sheet (slide-out panel)
- • `components/ui/dropdown-menu.tsx` — shadcn DropdownMenu
- • `components/user-menu.tsx` — desktop dropdown (Profil, Podpora, Odhlásit)
- • `components/mobile-nav.tsx` — mobilní hamburger Sheet
- • `components/navbar.tsx` — responsivní layout s `md:` breakpointem, Bell vždy viditelný, CTA Registrovat vždy viditelná
- • Desktop (md+) přihlášený: Logo | Dashboard | Podpora | Bell | [Jméno ▼]
- • Mobil přihlášený: Logo | Bell | [☰] → Sheet
- • Desktop nepřihlášený: Logo | Kontakt | Přihlásit | Registrovat
- • Mobil nepřihlášený: Logo | Registrovat | [☰] → Sheet
- Feat: Support Ticket System
- • `app/api/support-ticket/route.ts` — vytvoření ticketu (anon + auth), rate limit 3/5min, emaily, audit log
- • `app/api/support-ticket/reply/route.ts` — admin odpověď s emailem + in-app notifikací
- • `app/contact/page.tsx` — veřejný kontaktní formulář s pre-fill pro přihlášené
- • `app/support/page.tsx` — historie vlastních ticketů (auth required)
- • `app/admin/support/page.tsx` — admin správa: filtry, sorting, CSV export, inline reply
- • `lib/rate-limit.ts` — nový limit `supportTicket: 3/5min`
- • `admin/layout.tsx` — "Podpora" v navigaci
- • `admin/audit/page.tsx` — nové ACTION_LABELS + ENTITY_LABEL pro support tickety
- • `components/navbar.tsx` — odkaz "Kontakt" pro nepřihlášené, "Podpora" pro přihlášené
- • `app/page.tsx` — "Kontaktní formulář" ve footer sekci Kontakt
- • Tabulka `support_tickets` s RLS, indexy, auto-update trigger
- Fix: Konzistentní validace adresy ve wizardu
- • `components/address-autocomplete.tsx` — nový `onManualChange` callback (reset odvozených dat při manuální editaci textu)
- • `seller/new-lead/step-2-location.tsx` — pole Město/PSČ jsou readOnly (plní se z autocomplete), tlačítko "Pokračovat" vyžaduje souřadnice (lat/lng), varování při nezvolené adrese
- Fix: Konzistentní validace plochy (size_m2) mezi wizard a editací
- • `lib/utils/validation.ts` — `validateNumericInput()` (clamp v onChange), `isInRange()` (submit safety net)
- • `lib/constants.ts` — `SIZE_LIMITS` konstanty + `getSizeLimits()` helper
- • `seller/new-lead/step-3-parameters.tsx` — onChange validace na sizeM2/landAreaM2, dynamický max, disabled button kontrola
- • `seller/new-lead/page.tsx` — submit safety net před insertem
- • `seller/leads/[id]/edit/page.tsx` — onChange validace na všech sizeM2 inputech + landAreaM2, submit safety net
- Feat: Google OAuth pro sellery (F17)
- • `components/google-sign-in-button.tsx` — GoogleSignInButton komponenta (oficiální Google "G" ikona, loading/error state)
- • `app/auth/callback/route.ts` — smart redirect (admin→/admin, neověřený→/auth/verify, ověřený→/dashboard)
- • `app/auth/verify/page.tsx` — formulář pro zadání telefonu (Google OAuth seller nemá phone), dynamický popis dle stavu email verifikace
- • `app/auth/login/page.tsx` — divider "nebo" + GoogleSignInButton pod formulářem
- • `app/auth/register/seller/page.tsx` — GoogleSignInButton navrchu + divider "nebo vyplňte formulář"
- • `handle_new_user()` — detekce Google providera (`raw_app_meta_data->>'provider'`), automaticky `role=seller`, `email_verified_at=NOW()`, parsování `full_name` na `first_name`/`last_name`
- • Google provider zapnut (Client ID + Secret)
- • Redirect URLs nastaveny (wildcard `/**`)
- • Automatic Linking (výchozí chování Supabase)
- • OAuth 2.0 Client (Web application) — 3 JS origins, 1 redirect URI
- • App publikována (In production)
18. února 2026
- Feat: OG image pro sociální sítě (F15 dokončeno)
- Fix: Landing page — mobilní responsivita obrázků
- • **Hero** — přidán druhý `<Image>` s `lg:hidden` pod text (desktop zachován absolutní pozicí)
- • **WhySellersSection** — `order-last lg:order-first` místo `hidden lg:block`
- • **HowItWorksSection** — odstraněn `hidden lg:block`, responzivní výšky
- SEO: F5 dokončení — robots.txt, sitemap.xml, JSON-LD, Image optimalizace
- • `public/robots.txt` — pravidla pro crawlery (Disallow: /admin, /api, /dashboard, /auth, /seller, /broker, /notifications, /checkin)
- • `app/sitemap.ts` — statický sitemap (/, /privacy, /terms, /terms/broker, /changelog)
- • Organization schema (OneManDev s.r.o., adresa, logo, email)
- • WebSite schema (název, URL, jazyk)
- • FAQPage schema (6 otázek z FAQ sekce)
- • Nahrazeny raw `<img>` za Next.js `<Image>` ve 4 souborech (page.tsx, why-sellers-section, how-it-works-section, compare-section)
- • Hero building: `priority={true}` pro LCP optimalizaci
- • Automatický WebP + srcset pro všechny obrázky
- • hero-building.png: "Moderní bytový dům s balkóny — prodej nemovitosti"
- • compare-landscape.png: "Realitní makléři konzultují prodej nemovitosti"
- • how-it-works.png: "Podání rukou při dohodě o prodeji nemovitosti"
- • compare-house.png: "Rodinný dům — srovnání nabídek makléřů"
- Security: Hardening — CRON auth, Next.js CVE, race conditions, IP spoofing, headers
- • 3 CRON Edge Functions (`watchdog-check` v10, `expire-leads` v4, `checkin-send` v4) — přidán service_role_key auth check, deploy do produkce
- • Next.js 16.0.7 → 16.1.6 — oprava known CVEs (DoS, source exposure)
- • `checkin-respond` — atomic UPDATE (race condition) + `Referrer-Policy: no-referrer` (token leak)
- • `verify-email` — rate limit 10/min + atomic UPDATE (race condition)
- • `getClientIp()` helper — anti-spoofing IP extraction (x-real-ip preference, poslední IP z x-forwarded-for)
- • 11 API routes aktualizováno na `getClientIp()`
- • Security headers v `next.config.ts` — HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy
- • Smazán legacy `backend/` adresář (nepoužívaný Python prototyp)
- • `next.config.ts` — security headers + turbopack.root
- • `lib/rate-limit.ts` — `getClientIp()`, `verifyEmail` preset
- • `auth/verify-email/route.ts` — atomic update + rate limit
- • `api/checkin-respond/route.ts` — atomic update + safeRedirect
- • 10 dalších API routes — `getClientIp()` migration
- • 6 test souborů — mock updates pro `getClientIp`
- Content: Landing page SEO + faktická správnost
- • `app/layout.tsx` — meta title, description, OG/Twitter tagy (SEO keywords "prodej nemovitosti")
- • `app/page.tsx` — H1 s SEO keywords, hero popis, trust badges, copyright 2025→2026
- • `components/landing/why-sellers-section.tsx` — H2, podtitulek, "zdarma" místo "platíte při úspěchu"
- • `components/landing/how-it-works-section.tsx` — H2 se SEO keywords, kroky bez "500m" a "z okolí"
- • `components/landing/compare-section.tsx` — H2, features (jen % provize, semafor popis)
- • `components/landing/for-brokers-section.tsx` — provize specifikována (20 %)
- • `components/landing/testimonial-section.tsx` — fiktivní testimonial nahrazen value proposition kartami
- • `components/landing/faq-section.tsx` — semafor opraven, min. provize 5 000 Kč, poloha nemovitosti
- • `components/hero-buttons.tsx` — CTA text, link na /auth/register/seller
- • Cenový semafor: zelený/žlutý/červený → štítky Vysoká/Nízká cena
- • "% vs. fixní" provize → jen procentuální
- • "Platíte až při úspěchu" → prodávající neplatí vůbec
- • "Makléři z okolí/regionu" → odstraněno (makléř se může přihlásit odkudkoli)
- • "Až 5 nabídek" → "5 nabídek" (odemknou se při 5)
- • Fiktivní testimonial "Jan N." → 3 karty (5 nabídek, 0 Kč, ověření makléři)
- • Copyright 2025 → 2026
- • Minimální provize 5 000 Kč bez DPH doplněna do FAQ
16. února 2026
- Fix: Match inline select — validace state machine
- • `app/admin/matches/page.tsx` — přidána `VALID_TRANSITIONS` mapa, validace v `handleStatusChange`, filtr v `<SelectContent>`
- • Nová konstanta `VALID_TRANSITIONS` definuje povolené přechody pro každý stav
- • `handleStatusChange` validuje přechod před UPDATE — nevalidní odmítne s toast chybou
- • `<SelectContent>` zobrazuje jen aktuální stav + validní cíle (finální stavy jsou readonly)
- Testing Matrix — Kompletní testovací matice
- • `TESTING-MATRIX.md` — kompletní testovací matice v markdown (16 sekcí, 471 test casů)
- • `TESTING-MATRIX.csv` — CSV export pro Excel/Google Sheets (UTF-8 BOM, středníky)
- F1: Právní dokumenty v2.0
- • `app/terms/page.tsx` — Podmínky služby v2.0 (16 sekcí, definice, pravidla, match, monitoring, hodnocení, suspenze, spory)
- • `app/terms/broker/page.tsx` — Rámcová smlouva pro makléře v2.0 (19 sekcí, provize 20%, anti-bypass, sankce, watchdog, check-in)
- • `app/privacy/page.tsx` — Zásady ochrany osobních údajů v2.0 (13 sekcí, 14 účelů zpracování, GDPR práva, zpracovatelé, třetí země)
- • `LEGAL-TERMS.md` — zdrojový markdown Podmínek služby
- • `LEGAL-TERMS-BROKER.md` — zdrojový markdown Rámcové smlouvy
- • `LEGAL-PRIVACY.md` — zdrojový markdown Zásad ochrany osobních údajů
- • `PRAVNIK-BRIEF.md` — aktualizovaný podklad pro právníka (9 sekcí, 33 otázek)
- • Odstraněn warning banner „Toto je pracovní verze smlouvy" z broker terms
- • Cross-linky mezi dokumenty (VOP → Rámcová smlouva → Privacy)
- • Tabulka sankcí (obcházení 100k Kč / 3x provize, nepravdivý check-in 50k Kč)
- • Tabulka zpracovatelů (Supabase IE, Hetzner DE, Resend US, Vonage NL, Sentry US)
- • SCCs/DPF pro přenosy do USA (Resend, Sentry)
- • ČOI + ODR odkaz pro spotřebitele, ÚOOÚ kontakt
- F18: Admin 2FA (TOTP)
- • `hooks/useMfa.ts` — centralizovaný MFA hook (enroll, verify, unenroll, AAL check)
- • `app/auth/mfa-verify/page.tsx` — verifikační stránka po loginu (6-ciferný OTP, auto-submit)
- • `app/auth/mfa-setup/page.tsx` — 3-krokový setup wizard (instrukce → QR kód → ověření)
- • `components/mfa-setup-section.tsx` — 2FA správa v profilu (setup/unenroll dialogy)
- • `hooks/useAuthGuard.ts` — nová `requireMfa` option, async KONTROLA 3.5 (AAL level + listFactors)
- • `app/admin/layout.tsx` — zapnuto `requireMfa: true`
- • `app/auth/login/page.tsx` — admin redirect na `/admin` (useAuthGuard zachytí MFA)
- • `app/profile/page.tsx` — MfaSetupSection v tabu Účet (pouze admin)
- • `app/admin/audit/page.tsx` — 4 nové audit labels (mfa_enroll/unenroll/verify_success/verify_failed)
- • `hooks/index.ts` — export useMfa
- Refaktoring: Dead code cleanup
- • `leads.extra_slots_unlocked` — nikdy implementovaný mechanismus 5+2 slotů
- • `leads.floor_plan_url` — nikdy implementovaný upload půdorysu
- • `bids.proof_description` — nikdy implementovaný popis důkazu
- • `matches.invoice_generated` — odložená Fakturoid integrace
- • `seller_leads_summary` VIEW — nikde nedotazovaný
- • `hooks/useFetchData.ts` — nikdy importovaný hook
- • `app/api/notifications/route.ts` — nepoužívaná API route (frontend dotazuje Supabase přímo)
- • `components/ui/form.tsx` — nepoužívaný shadcn Form wrapper
- • `proxy.ts` — staging proxy bez middleware
- • 9 mrtvých exportů z constants.ts (ENERGY_RATING_LABELS, ENCUMBRANCE_VALUES, NETWORK_LABELS aj.)
- • 5 mrtvých type aliasů z types.ts (Match, CheckinToken, BrokerRating aj.)
- • `validateParams()` z api.ts, `notifications` rate limit
- • 6 console.log v useAuthGuard.ts nahrazeno komentáři
- • Autogenerované TypeScript typy synchronizovány se Supabase
13. února 2026
- Refaktoring: New-lead wizard split
- • `types.ts` — FormData, StepProps, INITIAL_FORM_DATA
- • `step-1-property-type.tsx` — výběr typu nemovitosti
- • `step-2-location.tsx` — adresa / mapa + katastr
- • `step-3-parameters.tsx` — parametry per typ nemovitosti
- • `step-4-photos.tsx` — upload fotek interiér/exteriér
- • `step-5-summary.tsx` — shrnutí + čestné prohlášení
- Feat: F16 — Export CSV z admin tabulek
- • `lib/csv-export.ts` — utilita: BOM pro Excel, středník jako oddělovač, escapování
- • `components/csv-export-button.tsx` — reusable tlačítko s Download ikonou
- • `__tests__/utils/csv-export.test.ts` — 16 testů (escapování, BOM, formátování, edge cases)
- Feat: F13 — Hodnocení makléřů
- • `/seller/rate/[matchId]` — formulář s 4 sekcemi (povinné hvězdy + doporučení, volitelné kategorie, kontextové otázky z nabídky, komentář max 500 znaků)
- • `/admin/ratings` — tabulka s moderací (schválit/zamítnout), filtry dle statusu, rozbalitelné detaily
- • `star-rating.tsx` — interaktivní star picker (1-5 hvězd, brand barvy)
- • CTA banner v seller lead detailu "Ohodnoťte svého makléře"
- • DB tabulka `broker_ratings` s RLS (seller insert/select, admin select/update, broker select approved)
- • API `/api/submit-rating` s rate limitingem, validací, audit logem
- • Edge Function `send-notification-email` v20 — nový typ `rating_request`
- • Trigger v `handleConfirmSale()` — email + in-app notifikace sellerovi
- Feat: F19 — Detekce duplicitních leadů
- • Stejná parcela + stejná (nebo obě prázdná) jednotka → blokace s odkazem na existující poptávku
- • Stejná parcela + různá jednotka → projde (jiná nemovitost)
- • Bez parcelního čísla → kontrola se přeskakuje
- • Kontrola v `handleSubmit()` přímým Supabase dotazem (RLS, žádný nový API endpoint)
- • UI blok s informací a tlačítkem na existující poptávku
- • DB partial index `idx_leads_duplicate_check` pro rychlé vyhledávání
- Feat: F11 — Pokročilé filtry leadů pro makléře
- • Typ nemovitosti — badge toggle buttons (Vše + 5 typů, klik na vybraný zruší)
- • Subkategorie — kontextový Select (byt→dispozice, dům→kategorie, pozemek→kategorie...)
- • Stav nemovitosti — Select (novostavba, po rekonstrukci, před rekonstrukcí...)
- • Plocha m² — dva Input pole (od/do) s debounce 300ms
- • Řazení — Select (nejnovější, brzy vyprší, málo slotů, největší plocha)
- • Export `getLeadSubcategory` z `lead-card.tsx` (bylo privátní)
- • Fix `parseLeadParams` — přidáno parsování `rooms`
- • Tab počty se počítají z `advancedFilteredLeads` (ne z `locationFilteredLeads`)
- • "Zrušit filtry" resetuje všechny filtry včetně nových
- • Counter "X z Y" reflektuje aktuální filtrovaný+seřazený stav
12. února 2026
- Fix: In-app notifikace se nezobrazovaly
- • `hooks/useNotifications.ts` — fetch() nahrazen primymi Supabase dotazy, hook prijima `supabase` klienta jako parametr
- • `components/notification-bell.tsx` — pridan import `useAuth`, predava `supabase` do hooku
- • `app/notifications/page.tsx` — vsechny fetch() nahrazeny primymi Supabase dotazy pres `useAuthGuard().supabase`
- Feat: F9 — Audit Log
- • Nova tabulka `audit_logs` (UUID PK, user_id, user_role, action, entity_type, entity_id, changes JSONB, metadata JSONB)
- • RLS: admin-only cteni, authenticated INSERT (user_id = auth.uid() nebo NULL)
- • 4 indexy: created_at DESC, action, (entity_type, entity_id), user_id
- • `lib/audit.ts` — `logAuditAction()` fire-and-forget (nikdy neblokuje UX)
- • Admin: suspend_user, unsuspend_user, change_role, delete_user, confirm_sale, issue_invoice, record_payment, cancel_match, revert_to_watching, approve_sale_report, reject_sale_report, change_match_status
- • Broker: report_sale, submit_bid, checkin_respond
- • Seller: create_lead, create_match, extend_lead
- • `/admin/audit` — filtry (akce, entita, uzivatel, datum od-do), razeni, paginace
- • Rozbalitelne radky s detailem zmen a metadat
- • Badge barvy podle role (admin fialova, broker modra, seller seda, system seda)
- • Navigacni odkaz v admin sidebar (ScrollText ikona)
- Feat: F15 (částečně) + F5 (OG tagy) — Umami Analytics + Open Graph
- • **Cookieless tracking** — GDPR bez consent banneru, žádné cookies
- • **Podmíněné načtení** — `<Script>` se renderuje pouze pokud jsou nastaveny `NEXT_PUBLIC_UMAMI_URL` + `NEXT_PUBLIC_UMAMI_WEBSITE_ID`
- • **5 custom events** (konverzní body):
- • **`trackEvent()` utilita** (`lib/utils/analytics.ts`) — production-only, v dev loguje do konzole, fire-and-forget
- • **UTM parametry** — Umami trackuje automaticky (utm_source, utm_medium, utm_campaign)
- • `metadataBase`, `openGraph`, `twitter` metadata v layout.tsx
- • Title: "Správný Makléř – Nechte makléře soutěžit o vaši nemovitost"
- • Description: "Zadejte nemovitost zdarma a získejte až 5 nabídek od ověřených makléřů."
- • OG image placeholder: `og-image.png` (1200×630, zatím nutno vytvořit manuálně)
- UX: Sloučení notifikačních karet v profilu makléře
- • **Collapsible panel** — řádek "Nové poptávky v oblasti" je klikatelný, rozbalí výběr krajů přímo pod sebou
- • **RadioGroup** — přepínač "Všechny kraje" / "Vybrané kraje" s grid checkboxů 14 krajů
- • **Dynamický popisek** — pod-text řádku zobrazuje aktuální stav ("Všechny kraje" nebo "Vybrané kraje (N)")
- • **Disabled stav** — panel krajů se deaktivuje pokud jsou oba kanály (email + in-app) vypnuté
- • **Beze změny backendu** — `preferred_regions` se ukládá stejně jako dosud
11. února 2026
- Feat: Granulární notifikační preference (JSONB matice)
- • **6 typů notifikací**: Nové poptávky, Match, Potvrzení prodeje, Check-in, Nabídky připraveny, Expirace poptávky
- • **2 kanály**: Email + In-app — každý typ lze nezávisle zapnout/vypnout
- • **Opt-out model** — výchozí stav je "vše zapnuto", uživatel vypíná co nechce
- • **Zamčené in-app** — operační notifikace (match, check-in) mají in-app vždy zapnutý
- • **Backend respektuje preference**: 3 SQL trigger funkce, Edge Functions `expire-leads` v3 + `checkin-send` v3, admin match detail
- • **Zpětná kompatibilita** — staré sloupce se stále zapisují paralelně
- • **Admin panel** — zobrazení JSONB preferencí v detailu uživatele
- Fix: Staging password loop po přihlášení
- • **Nahrazení HTTP Basic Auth za cookie-based ochranu** — `proxy.ts` nyní kontroluje HttpOnly cookie `staging_access` (SHA-256 hash hesla)
- • **Nová stránka `/auth/staging`** — formulář na zadání staging hesla s brand designem
- • **Nový API endpoint `/api/staging-auth`** — ověří heslo, nastaví cookie (30 dní, HttpOnly, SameSite=Lax)
- • Cookie se posílá automaticky na všechny requesty (fetch, SSR, navigace) → žádná smyčka
- Feat: F12 — In-app notifikace (zvoneček + dual-write)
- • **DB**: Tabulka `notifications` s RLS (select_own, update_own, insert_authenticated)
- • **SQL funkce `create_notification()`** (SECURITY DEFINER) — volatelná z Edge Functions i API routes
- • **Hook `useNotifications`** — polling 60s, mark as read, mark all as read
- • **Komponenta `NotificationBell`** — zvoneček v navbaru s badge nepřečtených, popover dropdown s posledními 10 notifikacemi
- • **API `/api/notifications`** — GET (paginated, filtr unread_only) + PATCH (mark read: single ID nebo all)
- • **Stránka `/notifications`** — kompletní seznam, seskupení po dnech (Dnes/Včera/Starší), filtr nepřečtené
- • **Dual-write retrofit**: `send-notification-email` v16 — po odeslání emailu i `create_notification()` RPC
- • **Dual-write**: report-sale (admin notifikace), watchdog-check (admin alert), expire-leads (seller upozornění)
- • **Integrace do navbaru** — NotificationBell mezi Dashboard linkem a jménem uživatele
- Feat: F10 — Match check-in systém (3-měsíční dotazník obou stran)
- • **DB**: Tabulka `checkin_tokens` (tokenizované 1-click odpovědi) + sloupce v matches (last_checkin_at, next_checkin_at, checkin_round, checkin_status)
- • **Edge Function `checkin-send`** v1 (CRON denně 9:00 UTC) — query matches kde `next_checkin_at <= NOW()`, email + in-app notifikace obou stran
- • **Email šablony**: `checkin_broker`, `checkin_seller`, `checkin_reminder` — 3 CTA tlačítka (Probíhá / Prodej dokončen / Problém)
- • **API `/api/checkin-respond`** — token-based 1-click odpověď z emailu (bez přihlášení, GET s redirect)
- • **API `/api/checkin-respond-web`** — auth-based odpověď z webové aplikace (POST s JWT)
- • **Vyhodnocovací logika**: obě "ongoing" → ok; obě "completed" → nahlásit prodej; nesoulad → red_flag + admin notifikace
- • **Upomínka**: 14 dní bez odpovědi → reminder email; 2+ neodpovědí → red flag
- • **Komponenta `CheckinBanner`** — brand card s gradient border, 3 tlačítka, success stav
- • **Integrace**: broker match detail (`/broker/matches/[id]`) + seller lead detail (`/seller/leads/[id]`)
- • **Stránka `/checkin/success`** — potvrzení po odpovědi z emailu + "strašák" monitoring katastru
- • **Admin UI**: check-in sloupec v tabulce matchů (ok/pending/red_flag badge), red flag filtr, check-in historie v match detailu (tabulka odpovědí)
- Feat: F4 — Sentry integrace (error tracking)
- • **Client**: Zachytává JS errory v prohlížeči, Session Replay (100% sessions s errorem)
- • **Server**: API routes, Server Components, server actions
- • **Edge**: Middleware errory
- • `global-error.tsx` — React error boundary pro App Router
- • `instrumentation.ts` — registrace Sentry pro Node.js a Edge runtime
- • `next.config.ts` — `withSentryConfig` wrapper, auto-instrumentace
- • Build přepnut na webpack (`next build --webpack`) — Turbopack má bug s `node:inspector` v názvu chunku na Windows
- • Sentry posílá data pouze v produkci (`enabled: process.env.NODE_ENV === "production"`)
- Fix: F3 — Account Deletion Cleanup (kritický bug fix)
- • **KRITICKÝ BUG FIX**: `auth.admin.deleteUser()` spouštělo CASCADE chain (auth.users → profiles → leads/bids/matches), čímž mazalo veškerá data včetně matchů a faktur
- • Nahrazeno za **ban + anonymizaci**: `updateUserById()` s ban_duration ~100 let + anonymizace emailu
- • **Broker**: Nelze smazat s aktivními matchi (watching/sold/invoiced) — vrací ACTIVE_MATCHES error s počtem a detaily
- • **Seller**: Automatické zrušení aktivních leadů (→ cancelled) + odmítnutí pending bidů (→ rejected)
- • **Rozšířená anonymizace**: nově i first_name, last_name, bank_account, office_*, network_*, verifikační pole, terms_*
- • Smazání broker_service_profiles při mazání brokera
- • DB migrace: rozšíření `account_deletion_audit` o first_name, last_name, bank_account, leads_cancelled, bids_rejected
- • Role-specific popis v delete dialogu (broker vs seller)
- • Upozornění pro brokery o blokaci při aktivním spárování
- • Parsování ACTIVE_MATCHES chyby z Edge Function s detailní zprávou
- Feat: F6 — Potvrzovací dialog před odesláním nabídky
- • Validace formuláře (suspended check, služby) → otevření dialogu místo přímého odeslání
- • Rekapitulace: odhadovaná cena, provize (%), služby (badge tagy), další služby, zpráva (zkrácená na 100 znaků)
- • Tlačítka "Zpět k úpravám" a "Odeslat nabídku"
- • Doplňuje F2 (race condition ochrana) — poslední šance zkontrolovat údaje před INSERT
- Feat: F2 — Race condition ochrana nabídek (BEFORE INSERT trigger)
- • **`check_bid_limits()`** — kontrola `max_bids` limitu a stavu leadu před vložením nabídky
- • **`FOR UPDATE` lock** na řádku leadu → atomická kontrola (serializuje souběžné INSERTy)
- • Kontrola stavu leadu (musí být `active`) — custom ERRCODE `P0001`
- • Počítání pouze ne-rejected bidů proti limitu — custom ERRCODE `P0002`
- • `SECURITY DEFINER` + `SET search_path = public`
- • Rozlišení chybových hlášek: neaktivní lead, plný lead, duplicitní nabídka
- • Re-fetch lead dat po chybě (aktualizace UI při změně stavu)
- • Kontrola slotů: pokud `bid_count >= max_bids` → "Všechna místa obsazena" místo formuláře
9. února 2026
- Remove: Testing banner odebrán z layoutu
- Feat: Floating navbar + windowed landing page layout
- • Zaoblené rohy (`rounded-2xl`), brand purple shadow, border
- • Responsivní margins (`mx-4 sm:mx-6 lg:mx-8`), max šířka `max-w-7xl`
- • Sticky s odsazením od vrchu (`top-3 mt-3`)
- • Backdrop blur efekt zachován
- • Všechny sekce mají shodné margins a max-width jako navbar
- • Barevné sekce (Hero, HowItWorks, ForBrokers, Testimonial, CTA, Footer) se zaoblenými rohy
- • Footer s větším vnitřním paddingem pro lepší čitelnost
- Fix: Chybějící soubory v git repo (Coolify build fail)
- Fix: Dropdown adresního našeptávání oříznutý v profilu
- Feat: Role přesunuta z pruhu do navbaru
- • Jméno uživatele + role s barevnou tečkou
- • Na mobile jen křestní jméno
- • Klik vede na profil
- • Smazána komponenta `role-indicator.tsx`
- Feat: České skloňování křestních jmen (5. pád / vokativ)
- • Heuristická pravidla pro 5. pád českých křestních jmen (bez externích závislostí)
- • Pokrytá pravidla: -a→-o (Jana→Jano), -ek→-ku (Marek→Marku), -ch→+u (Vojtěch→Vojtěchu), sykavky +i (Tomáš→Tomáši), -k→-ku (Patrik→Patriku), tvrdé souhlásky +e (Michal→Michale)
- • Nepravidelné tvary: Petr→Petře, Pavel→Pavle, Karel→Karle, Zdeněk→Zdeňku
- • Neměnné koncovky: -ie (Marie), -í (Jiří), -o (Hugo), -e (René)
- • Cizí a neznámá jména zůstávají beze změny (fallback)
- • Zachování velikosti písmen (PETR→PETŘE, petr→petře)
- • 46 nových testů (mužská, ženská, cizí jména, edge cases)
- • Celkem: **164 testů v 14 suites**
- Feat: Brand design refresh — kompletní migrace barev na brand paletu
- • **28+ stránek** — admin, auth, broker, seller, profil, dashboard, landing
- • **4 komponenty** — button, card, bid-comparison-table, lead-card
- • **1 config** — constants.ts (status badges, PENB barvy)
- • **Button**: nový `lavender` variant (`bg-[#AF96FC]`) pro auth a sekundární akce
- • **Card**: `rounded-2xl`, `shadow-md shadow-[#6039f6]/5`, lepší hover efekty
- • **Auth layout wrapper** (`auth-layout.tsx`) — reusable gradient pozadí pro 6 auth stránek
- • **Seller wizard stepper** — gradient kroužky, checkmark ikony, scale animace, smooth progress bar
- • **Layout** — `min-h-[calc(100vh-4rem)]` fix pro správné pozicování footeru
- • Active/Success: `bg-[#e0d5f5] text-[#220860]` (místo green)
- • Error messages: `bg-[#f5f0ff] border-[#220860]/10 text-[#220860]` (místo red)
- • Warning/Info: `bg-[#f5f0ff] border-[#e0d5f5]` (místo amber/yellow)
- • PENB A-B: `#6039f6`, C: `#AF96FC`, D-G: `#220860` s klesající opacitou
6. února 2026
- Feat: Provizní a fakturační systém
- • `invoice_number` — číslo faktury (reference na externí systém)
- • `invoice_issued_at` — datum vystavení faktury
- • `invoice_due_date` — datum splatnosti
- • `invoice_paid_at` — datum zaplacení
- • `actual_commission_percent` — skutečná provize makléře (pokud se liší)
- • `sale_confirmed_at` — datum potvrzení prodeje adminem
- • `admin_notes` — poznámky admina
- • `broker_response_deadline` — deadline pro reakci makléře
- • **Časová osa** — 5 kroků: spárováno → prodáno → potvrzeno → fakturováno → zaplaceno
- • **Info sekce** — nemovitost, kontakty stran (vč. emailů), nabídka makléře
- • **Prodej** — detekce plomby, potvrzení prodeje, editace prodejní ceny
- • **Fakturace** — live výpočet provize (20 % z nabídkové, min. 5 000 Kč + DPH 21 %)
- • **Akční tlačítka** — kontextová dle stavu (Potvrdit prodej, Vystavit fakturu, Zaznamenat platbu, Zrušit)
- • **Poznámky** — interní poznámky admina
- • Řádky klikací → odkaz na detail stránku
- • Nový sloupec "Faktura" (číslo + ikona stavu platby/splatnosti)
- • **`sale_confirmed`** — výzva makléři po potvrzení prodeje adminem (dynamický předmět, deadline 14 dní, nabídková cena)
- • **`payment_reminder`** — upomínka o neuhrazené faktuře (číslo, částka, dny po splatnosti, varování o suspenzi po 30 dnech)
- • Přesný provizní vzorec s příkladem výpočtu
- • Pravidla pro smlouvu (7 bodů)
- • Povinnosti makléře + sankce (nenahlášení, nepravdivé údaje, neuhrazení)
- • Informační klauzule o monitoringu katastru (veřejný registr, souhlas nepotřeba)
- • Business proces (7 kroků s lhůtami)
27. ledna 2026
- Fix: Error handling - toast notifikace na zbývajících 9 místech
- • `fetchProfile` — Supabase error po vyčerpání retries → "Nepodařilo se načíst profil. Zkuste obnovit stránku."
- • `fetchProfile` — neočekávaná výjimka → "Nepodařilo se načíst profil. Zkuste obnovit stránku."
- • `signOut` — chyba při odhlášení → "Chyba při odhlašování."
- • `fetchData` — načtení sítí selhalo → "Nepodařilo se načíst sítě."
- • `toggleNetworkActive` — přepnutí stavu → "Nepodařilo se změnit stav sítě."
- • `deleteNetwork` — smazání sítě → "Nepodařilo se smazat síť."
- • `rejectCustomNetwork` — zamítnutí → "Nepodařilo se zamítnout síť."
- • `fetchUsers` — načtení uživatelů → "Nepodařilo se načíst uživatele."
- • `fetchMatches` — načtení spárování → "Nepodařilo se načíst spárování."
- • `getSession` error — uživatel je přesměrován na login
- • Init error / timeout — fallback na `unauthenticated` funguje
26. ledna 2026
- Feat: Kontrola duplicitního telefonu při registraci
- • **Partial UNIQUE index** na `profiles.phone` (WHERE phone IS NOT NULL)
- • Vyčištěny existující duplicity (8+2 účtů se sdíleným telefonem)
- • **POST `/api/check-availability`** - kontrola dostupnosti telefonu
- • **Seller + Broker** - phone check před `supabase.auth.signUp()`
- • Pokud telefon obsazen → chybová hláška "Tento telefon je již registrován"
- • Pokud API selže → pokračuje (DB UNIQUE constraint je safety net)
- • **8 nových testů** v `__tests__/api/check-availability.test.ts`
- Feat: Pozastavení účtu (Admin Suspension)
- • Nové sloupce v `profiles`: `status` ('active'/'suspended'), `suspension_reason`, `suspended_at`, `suspended_by`
- • CHECK constraint na status
- • Partial index `idx_profiles_status` (WHERE status != 'active')
- • **`is_user_active(user_id)`** - SECURITY DEFINER helper funkce
- • RLS INSERT policies na `bids` a `leads` rozšířeny o `AND is_user_active(auth.uid())`
- • `get_users_with_email()` rozšířena o `status` a `suspended_at`
- • **User detail** (`/admin/users/[id]`):
- • **Users list** (`/admin/users`):
- • **useAuthGuard** - nový flag `isSuspended` (admin role vyjmuta)
- • **Dashboard** - varovný banner (červený gradient) pokud pozastaven
- • **Broker bid submit** - blokace odeslání nabídky + disabled tlačítko
- • **Seller new lead** - blokace vytvoření poptávky + disabled tlačítko
- • **`/auth/suspended`** (nová stránka) - info o pozastavení, důvod, kontakt, odhlášení
- • Rozšířeny typy `Profile` (Row/Insert/Update) o suspension pole
- • Nová funkce `is_user_active` ve Functions typech
- Admin: Sorting + Pagination na všech admin tabulkách
- • **`hooks/useAdminTable.ts`** - generický hook pro sorting + pagination stav
- • **`hooks/useDebounce.ts`** - debounce pro search inputy (300ms default)
- • **`components/sortable-table-head.tsx`** - klikatelný TableHead se sort ikonami
- • **`components/table-pagination.tsx`** - paginační ovládání pod tabulkou
- • **Users** - sortovatelné: Jméno, Role, Firma, Registrace + debounced search
- • **Leads** - sortovatelné: Typ, Město, Prodávající, Stav, Nabídky, Vytvořeno
- • **Bids** - sortovatelné: Odhad ceny, Provize, Stav, Datum; odstraněn ADMIN_PAGE_LIMIT
- • **Matches** - sortovatelné: Datum, Odhad ceny, Provize, Stav prodeje; odstraněn ADMIN_PAGE_LIMIT
- • `DEFAULT_ADMIN_PAGE_SIZE = 20` (nová)
- • `ADMIN_PAGE_LIMIT` ponechán pro zpětnou kompatibilitu
- 🧹 ESLint: Oprava všech 80 problémů (6 errors + 74 warnings)
- • `bid-comparison-table.tsx` — `set-state-in-effect` → lazy useState initializer (nahrazuje useEffect)
- • `photo-lightbox.tsx` — `set-state-in-effect` → "adjusting state during render" pattern
- • `price-input.tsx` — `set-state-in-effect` → displayValue odvozeno přímo z prop (bez useState/useEffect)
- • `useAuthGuard.ts` — `set-state-in-effect` (2×) → eslint-disable (redirect flag potřebuje useState pro rendering)
- • ~45× `import/order` — automaticky opraveno (`eslint --fix`)
- • 9× nepoužívané proměnné/importy odstraněny
- • 7× `no-img-element` → eslint-disable (lightbox/upload blob URL)
- • 1× `alt-text` → přejmenování Lucide `Image` → `ImageIcon`
- • 2× `exhaustive-deps` → eslint-disable
- • 1× `import/no-duplicates` → eslint-disable-line (leaflet namespace import)
- • 2× nepoužívané props odstraněny z `CollapsibleCategorySection`
- • 1× TypeScript fix admin/bids (Supabase relace jako pole → transformace broker i lead)
- ⚡ Code Quality Overhaul - Komplexní audit a hardening (17 úkolů ve 4 fázích)
- 🔒 ZÁSADNÍ: Security Hardening
- • **Problém**: `/api/test-watchdog` bez jakékoliv autentizace - kdokoli mohl volat ČÚZK API
- • **Řešení**: JWT token + admin role kontrola přes Supabase server client
- • Soubor: `frontend/src/app/api/test-watchdog/route.ts`
- • **Problém**: `watchdog-check` Edge Function posílala `service_role_key` jako Bearer token přes HTTP
- • **Řešení**: Přímé volání Resend API místo HTTP volání `send-notification-email`
- • Soubor: `supabase/functions/watchdog-check/index.ts`
- • Přidáno do `.gitignore`: `tmpclaude-*`, `nul`, `frontend/nul`, `frontend/tmpclaude-*`
- • Smazáno 29 dočasných souborů z repozitáře
- • **Nový soubor**: `frontend/src/lib/rate-limit.ts` - in-memory sliding window
- • Aplikováno na 5 API routes:
- • Automatický cleanup paměti každých 5 minut
- 🔒 ZÁSADNÍ: CI/CD Pipeline (z 0/10 na funkční)
- • **Nový soubor**: `.github/workflows/ci.yml`
- • Pipeline: `npm ci → eslint → tsc --noEmit → vitest → next build`
- • Spouští se na push a PR do master
- • Husky s monorepo setupem (`.git` v root, `package.json` ve `frontend/`)
- • lint-staged: `eslint --fix` na staged `.ts/.tsx` soubory
- • Soubor: `frontend/.husky/pre-commit`
- 🔒 ZÁSADNÍ: Testing (z 0/10 na funkční)
- • **Nový soubor**: `frontend/vitest.config.ts`
- • **3 test suites**:
- • **Extrakce**: IČO validace do sdíleného modulu `lib/utils/ico-validation.ts`
- Error Handling
- • `map-picker.tsx` - 2× `.catch(() => ({}))` → `toast.error()`
- • `seller/new-lead/page.tsx` - LV lookup → `toast.error()`
- • `profile/page.tsx` - loading networks/templates → `toast.error()`
- • `admin/matches/page.tsx` - `alert()` → `toast.error()`
- • Odstraněn debug `console.log` z `test-watchdog/route.ts`
- Refactoring
- • **Nový soubor**: `frontend/src/hooks/useFetchData.ts`
- • Nahrazuje opakující se `useState + useEffect` pattern
- • Exportován z `hooks/index.ts`
- • `ANONYMIZE_RADIUS_M = 500` (geo.ts)
- • `PARCEL_ZOOM_THRESHOLD = 17` (map-picker.tsx)
- • `COMPRESSION_THRESHOLD = 300 * 1024` (photo-upload.tsx)
- • `ADMIN_PAGE_LIMIT = 100` (admin stránky)
- • Soubor: `frontend/src/lib/constants.ts`
- • `zod` 4.1→4.3, `@supabase/supabase-js` 2.86→2.91, `@types/node` v20→v25
- • 5 nových indexů pro optimalizaci RLS a častých queries:
- Polish
- • `map-picker.tsx` - `role="region" aria-label="Mapa pro výběr lokace nemovitosti"`
- • `lead-location-map.tsx` - `role="region" aria-label="Mapa s přibližnou polohou nemovitosti"`
- • **Nový soubor**: `frontend/.prettierrc`
- • Přidáno: `eslint-config-prettier`, `eslint-plugin-import`
- • Import sorting pravidla (groups, no duplicates)
- • Prettier: `semi: true`, `singleQuote: false`, `printWidth: 100`
- • Nahrazen manuální auth kód za `useAuthGuard({ allowedRoles: ["admin"] })`
- • Soubor: `frontend/src/app/admin/layout.tsx`
- • Nahrazen `return params as LeadParams` za field-by-field validaci
- • Numerické, stringové a boolean pole validovány individuálně
- • Soubor: `frontend/src/lib/supabase/types.ts`
24. ledna 2026
- Feat: Admin users - zobrazení emailu
- • **`get_users_with_email()`** - vrací seznam uživatelů s emailem (join profiles + auth.users)
- • **`get_user_email_by_id(user_id)`** - vrací email konkrétního uživatele podle ID
- • Obě funkce kontrolují admin roli volajícího
- • `/admin/users` - email zobrazen v sloupci Kontakt, vyhledávání rozšířeno o email
- • `/admin/users/[id]` - email zobrazen v sekci Kontaktní údaje
- • `supabase/migrations/..._admin_users_email_functions.sql`
- • `frontend/src/app/admin/users/page.tsx`
- • `frontend/src/app/admin/users/[id]/page.tsx`
- Feat: Watchdog - Kompletní implementace automatické detekce plomby
- • **watchdog-check v4** - kontroluje matche se statusem `watching`
- • **send-notification-email v12** - nový typ `plomba_detected`
- • Příjemce: `admin@spravnymakler.cz`
- • Obsah: typ řízení, lokace, prodávající, makléř, odkaz na LV v ČÚZK
- • Automatická změna statusu matche na "Prodáno"
- • `/admin/settings` - Watchdog status změněn na "Aktivní"
- • `/admin/matches` - upozornění ⚠️ "Chybí k.ú. / parcela" u matchů bez katastrálních dat
- • `watchdog-weekly-check`: `0 8 * * 1` (pondělí 8:00 UTC)
- • `supabase/functions/watchdog-check/index.ts`
- • `supabase/functions/send-notification-email/index.ts`
- • `supabase/migrations/20260124150000_setup_watchdog_cron.sql`
- • `frontend/src/app/admin/settings/page.tsx`
- • `frontend/src/app/admin/matches/page.tsx`
- Security: Fix function_search_path_mutable
- • `update_networks_updated_at` - přidán `SET search_path = public`
- • `update_broker_service_profiles_updated_at` - přidán `SET search_path = public`
- • `ensure_single_default_service_profile` - přidán `SET search_path = public`
- • `supabase/migrations/20260124110000_fix_function_search_path.sql`
- Security: Fix SECURITY DEFINER views
- • **leads_for_brokers** - změna `security_invoker=false` → `security_invoker=true`
- • **admin_leads_summary** - změna `security_invoker=false` → `security_invoker=true`
- • View `admin_leads_summary` přidán do migrací (dříve jen v DB)
- • `supabase/migrations/20260124100000_fix_security_invoker_views.sql`
23. ledna 2026
- Design Enhancement - Micro-interactions a vizuální hloubka
- • **Brand shadows** - nové utility třídy `shadow-brand-sm/md/lg/xl` s fialovým nádechem
- • **Hover lift** - `.hover-lift` třída pro jemné vyzdvižení při hoveru
- • **Active press** - `.active-press` třída pro stisknutí při kliknutí
- • **Animace** - nové keyframes: `subtle-pulse`, `shimmer`, `float`, `bounce-subtle`
- • Přidán `active:scale-[0.98]` pro všechny varianty (press feedback)
- • Přidány shadow transitions: `hover:shadow-md` → `active:shadow-sm`
- • Gradient varianta s `hover:shadow-lg` a fialovým glow efektem
- • Změna `hover:shadow-md` → `hover:shadow-lg` pro výraznější stín
- • Přidán `hover:-translate-y-0.5` pro jemný lift efekt
- • Prodloužena transition na `duration-300`
- • Brand-colored focus ring: `focus-visible:border-[#6039f6]`
- • Glow efekt: `focus-visible:shadow-[0_0_0_4px_rgba(96,57,246,0.1)]`
- • Sticky pozice: `sticky top-0 z-50`
- • Glass efekt: `backdrop-blur-sm bg-background/95`
- • Přidán `shadow-sm` pro vizuální separaci
- • Logo hover: `group-hover:scale-105`
- • Lucide ikony pro každý status typ (Clock, Eye, CheckCircle, XCircle, TrendingUp/Down...)
- • Změna na `rounded-full` pro pill shape
- • Spinning animace pro "collecting" stav
- • Nový prop `hideIcon` pro zobrazení pouze textu
- • Nový prop `icon` s možnostmi: inbox, search, question, alert
- • Animovaný icon container s gradient pozadím (`animate-float`)
- • Změna na `border-2 border-dashed` styl
- • Gradient CTA tlačítko
- • Přidán `hover:-translate-y-1 hover:shadow-lg` pro výraznější lift
- • Ready state: `ring-2 ring-green-200 shadow-green-100`
- • Urgency indicator: `animate-subtle-pulse` při ≤2 zbývajících slotech
- • Přidán `shadow-sm` base shadow
- • Interaktivní badges: `[a&]:hover:shadow-md [a&]:hover:scale-105`
21. ledna 2026
- Brand Design 2025 - Kompletní vizuální redesign
- • **S-shaped ikona** s textem "Správný Makléř"
- • Gradient na ikonu: lavender → purple → dark
- • Bílá verze pro tmavé pozadí (`logo-white.svg`)
- • Favicon a PWA ikony aktualizovány
- • **Royal Plum**: `#220860` (primární tmavá)
- • **Brand Purple**: `#6039F6` (gradient střed)
- • **Lavender Glow**: `#AF96FC` (accent světlá)
- • **Text**: `#1f1463` (tmavě modrá)
- • Gradient pozadí: `from-[#220860] via-[#6039f6] to-[#AF96FC]`
- • Bílé logo, bílý text, bílá tlačítka
- • Modernizovaný vizuální styl
- Design: Gradient top borders na všech Card komponentách
- • Přidány gradient top borders na všechny Card komponenty v aplikaci
- • 74 karet aktualizováno ve 29 souborech
- • Brand gradient: `from-[#220860] via-[#6039f6] to-[#AF96FC]`
- • **Success** (zelená): email-verified, úspěšné akce
- • **Error** (červená): chybové stavy
- • **Pending** (oranžová): čekající položky (admin networks)
- • Admin: dashboard, users, leads, bids, matches, networks, settings + detaily
- • Seller: dashboard, new-lead wizard (5 kroků), lead detail, lead edit
- • Broker: dashboard, lead detail, bids, match detail
- • Auth: login, register, verify, forgot-password, reset-password, email-verified
- • Public: terms, terms/broker, privacy, changelog
- Legal: Údaje provozovatele aktualizovány
- • **IČO**: 24109193
- • **Sídlo**: Plzeňská 3352/156, Smíchov, 150 00 Praha 5
- • **E-mail**: info@spravnymakler.cz
- • Footer na všech stránkách
- • Terms of Service (`/terms`)
- • Privacy Policy (`/privacy`)
- • GDPR dialogy v registraci
- Feat: Watchdog příprava - nová databázová pole
- • `cadastral_code` - kód katastrálního území (pro ČÚZK API)
- • `house_number` - číslo popisné/evidenční (pro lookup jednotek)
- • Příprava pro automatické sledování změn na LV (plomba)
- • Umožní přesnější lookup jednotek v ČÚZK REST API KN
- • Watchdog CRON job: kontrola prodeje každé pondělí 8:00 UTC
25. prosince 2025
- Fix: Deploy opravy a Edge Functions
- • **admin/matches/page.tsx** - Supabase 1:1 relace (`lead`, `bid`) vracely pole místo objektu
- • Přidána kontrola `Array.isArray()` s fallbackem na první prvek
- • Oprava přístupu k `icon` property pro "all" status filter
- • **Příčina**: Default hodnota `matches.status` byla `'active'`, ale check constraint povoloval pouze `watching/sold/invoiced/paid/cancelled`
- • **Řešení**: DB migrace - změna default hodnoty na `'watching'`
- • `send-notification-email` v11
- • `send-verification-email` v6
- Admin: Stránka Spárování - kompletní redesign
- • **Sledováno** (modrá) - čeká se na prodej
- • **Prodáno** (zelená) - detekována plomba na LV
- • **Fakturováno** (oranžová) - faktura odeslána
- • **Zaplaceno** (fialová) - platba přijata
- • **Zrušeno** (šedá) - zrušený případ
- • **Naše provize** - kalkulace: `Odhad ceny × Provize makléře × 20%`
- • **Odkaz na LV** - klikací odkaz na ČÚZK nahlížení do katastru
- • Editovatelný select pro změnu stavu (admin only)
- • Filtry podle stavu prodeje (taby s počty)
- • Klikací odkazy: Nemovitost → lead detail, Prodávající → profil, Makléř → profil
- • Přechod z nested queries na snapshot sloupce (performance)
- • Nová RLS policy pro UPDATE matches (admins only)
- • DB migrace: `status` CHECK constraint + workflow stavy
- UX: Profil - sidebar tabs layout
- • **Broker (5 tabů)**: Profil, Firma, Notifikace, Šablony, Účet
- • **Seller (3 taby)**: Profil, Notifikace, Účet
- • **Profil** - osobní údaje (jméno, email, telefon)
- • **Firma** (broker) - firemní údaje, příslušnost k síti, adresa kanceláře (sloučeno)
- • **Notifikace** - preferované oblasti (broker) + emailové notifikace
- • **Šablony** (broker) - šablony služeb pro rychlejší vyplňování nabídek
- • **Účet** - zabezpečení (změna hesla) + nebezpečná zóna (smazání účtu)
- • Vertikální sidebar navigace vlevo, obsah vpravo
- • Na mobilu ikony bez textu (responzivní)
- • Adresa kanceláře přesunuta z vlastního tabu do tabu "Firma"
- • Radix Tabs s `orientation="vertical"`
- Feat: Změna hesla v profilu
- • Nová sekce "Zabezpečení" v tabu Účet
- • Dialog pro změnu hesla s validací aktuálního hesla
- • Kontrola délky nového hesla (min. 6 znaků)
- Feat: Re-verifikace telefonu při změně
- • Oranžové varování při změně telefonního čísla
- • Reset `phone_verified_at` na null při uložení
- • Platí pro seller i broker (telefon se předává při matchi)
- Feat: Jedna cena místo rozsahu
- • **Formulář nabídky**: Jedno pole "Odhadovaná prodejní cena" místo dvou (Od/Do)
- • **Databáze**: `price_min` = `price_max` (zpětná kompatibilita)
- • **Zobrazení**: `formatPriceRange()` zobrazí jednu cenu když min === max
- • **Existující data**: Aktualizována na horní hodnotu rozsahu
- • Jednodušší rozhodování pro prodávajícího
- • Větší tlak na makléře být přesný
- • Inspirace: Online odhady (Hypox) také uvádějí jednu cenu
- Feat: Cenový semafor - vylepšení
- • **Medián** z 5 nabídek (ne průměr - není ovlivněn extrémy)
- • **Trigger až při 5. nabídce** - do té doby klient nevidí porovnání
- • **Klasifikace**:
- • **Vysoká cena**: "Tato cena je výrazně vyšší než ostatní nabídky. Makléř možná přestřelil, aby vypadal atraktivně."
- • **Nízká cena**: "Tato cena je výrazně nižší než ostatní nabídky. Makléř možná podstřelil nebo nemá zkušenosti s touto lokalitou."
- • `calculate_price_label()` - výpočet s mediánem
- • `recalculate_lead_price_labels()` - přepočet všech nabídek leadu
- • `trigger_calculate_price_labels_at_max_bids()` - trigger při dosažení 5 nabídek
- UX: Mobile-friendly srovnávací tabulka
- • **Tlačítko "Skrýt"** v řádku "Akce" u každého makléře
- • Skryje makléře z porovnání (neovlivňuje databázi)
- • **Toast notifikace** s počítadlem "X makléřů skryto" a tlačítkem "Vrátit"
- • Možnost vrátit všechny skryté makléře jedním klikem
- • UX: Méně děsivé než "Vyřadit" - uživatel ví že akce je vratná
- • **Agregované řádky** pro každou kategorii (Příprava a Vizuál, Marketing, Právní, Poprodejní)
- • **Počítadlo X/Y** s progress barem (zelená výplň)
- • **Kliknutím rozbalení** všech služeb v kategorii
- • **Responzivní chování**:
- • **Tlačítko "Sbalit vše"** pro rychlé sbalení všech kategorií
- • **Nový řádek "Vybrat makléře"** na konci tabulky
- • **Sticky pozice** - viditelný při scrollu
- • **Zelené pozadí** pro vizuální odlišení od ostatních řádků
- • Tlačítka "Vybrat" přesunuta sem (pryč z hlavičky)
- • Nová závislost: `usehooks-ts` (useMediaQuery hook)
- • Integrace: `sonner` toast komponenta (shadcn/ui)
- • Responsive breakpoint: 768px (md)
- UX: Dvou-krokové potvrzení výběru makléře
- • **První klik** → tlačítko se změní na červené "Opravdu?"
- • **Druhý klik do 3 sekund** → provede se match
- • **Timeout 3s** → automatický reset zpět na "Vybrat"
24. prosince 2025
- UX: Srovnávací tabulka nabídek makléřů (styl Alza.cz)
- • **Sloupce = makléři** (#1-#5), řádky = služby seskupené do kategorií
- • **Hlavička**: odhad ceny, provize, tlačítko "Vybrat"
- • **28 služeb ve 4 kategoriích** s ✓/✗ ikonami
- • **Toggle "Zobrazit pouze odlišné parametry"** - filtruje služby kde se makléři liší
- • **Žluté zvýraznění** rozdílných řádků
- • **Popover pro zprávy** - kliknutím na zkrácený text se zobrazí celá zpráva
- • **Mobile-friendly**:
- UX: Barevné stavy poptávek pro prodávající
- • **Šedá** (waiting) - Čeká na nabídky (0 nabídek)
- • **Žlutá** (collecting) - Sbírá nabídky (1-4 nabídek)
- • **Zelená** (ready) - Připraveno k výběru (5/5 nabídek) + zelený ring
- • **Modrá** (matched) - Spárováno
- • `constants.ts` - nový `SELLER_LEAD_STATUS_CONFIG` a funkce `getSellerLeadDisplayStatus()`
- • `status-badge.tsx` - podpora typu "seller-lead"
- • `lead-card.tsx` - progress bar X/5, dynamický badge, `maxBids` prop
- • `seller/leads/page.tsx` - filtrovací taby (Všechny, Sbírá nabídky, K výběru, Spárováno)
- KRITICKÁ OPRAVA: Radix Checkbox Bug #2549 - infinite loop uvnitř form
- • **Problém**: React Error #185 při klikání na checkbox služeb v broker lead detail page
- • **Příčina**: Radix Checkbox uvnitř `<form>` dispatchuje syntetické click eventy přes interní `BubbleInput`
- • **Proč fungovalo v profile page**: ServiceCheckboxes tam byl uvnitř `<Dialog>`, NE uvnitř `<form>`
- • **Řešení**:
- • **Reference**: https://github.com/radix-ui/primitives/issues/2549
- KRITICKÁ OPRAVA: React Error #185 - Maximum update depth exceeded
- • **Problém**: Při rychlém zaškrtávání služeb v nabídkovém formuláři došlo k pádu aplikace
- • **Příčina**: Kombinace několika faktorů způsobovala kaskádové re-rendery:
- • **Řešení** (3 commity):
- UX: Accordion pro služby makléře
- • **Nahrazen scrollbox** - služby nyní v rozbalovacích sekcích (Accordion)
- • **4 kategorie jako AccordionItem** - Příprava a Vizuál, Marketing, Právní servis, Poprodejní servis
- • **Checkbox v hlavičce** - kliknutím vybere/odebere celou kategorii
- • **Počítadlo X/Y** u každé kategorie
- • **Žádný horizontální scroll** - jeden sloupec, čistý design
- • **Automatické otevření** - kategorie s vybranými službami se otevřou
- • **Nová závislost**: `@radix-ui/react-accordion`
- Služby makléře - checkboxy místo textu
- • **BROKER_SERVICES** - 28 služeb ve 4 kategoriích:
- • **ALL_BROKER_SERVICES** - flat array všech služeb
- • **BROKER_SERVICE_LABELS** - mapa klíč → label
- • **BrokerServiceKey** - TypeScript union type
- • **ServiceCheckboxes** - grid s checkboxy seskupenými do kategorií
- • **ServiceBadges** - kompaktní zobrazení jako barevné tagy
- • **Migrace**: `add_broker_services_and_profiles`
- • **Nové sloupce v `bids`**:
- • **Nová tabulka `broker_service_profiles`**:
- • **broker/leads/[id]** - nabídkový formulář
- • **profile** - správa šablon služeb (pouze broker)
- • **seller/leads/[id]** - zobrazení nabídek
- • **admin/leads/[id]** - admin panel
- • Aktualizovány typy v `lib/supabase/types.ts`
- • Nový type alias `BrokerServiceProfile`
- • Oprava: `as string[]` cast v service-checkboxes.tsx (type mismatch)
- • Oprava: `const brokerId = profile.id` před async funkcí (null check)
23. prosince 2025
- Code Review a Optimalizace
- • **XML Injection ochrana** - `escapeXml()` funkce pro VIES SOAP API (`ico-lookup/route.ts`)
- • **Validace parcelního čísla** - regex validace proti injection útokům (`cadastral-lookup/route.ts`)
- • **signOut() race condition** - správné pořadí: nejdřív Supabase logout, pak clear state (`AuthContext.tsx`)
- • **fetchProfile memory leak** - přidán `mountedRef` check + exponential backoff retry (`AuthContext.tsx`)
- • **photo-upload setTimeout leak** - cleanup timer při unmount (`photo-upload.tsx`)
- • **N+1 query fix** - Map lookup O(1) místo .filter() O(n) v loop (`dashboard/page.tsx`)
- • **ARES timeout** - přidán 10s timeout pomocí `fetchWithTimeout()` (`ico-lookup/route.ts`)
- • **Geo cache** - zvýšen z 1 hodiny na 7 dní pro RUIAN data (`cadastral-lookup/route.ts`)
- • **Leaflet CSS utility** - nový soubor `lib/utils/leaflet.ts` s `initializeLeaflet()` a `ensureLeafletCSSLoaded()`
- • **EnergyRatingSelect** - nová reusable komponenta pro PENB výběr (nahrazuje 3 duplicity)
- • **UnitNumberInput** - nová reusable komponenta pro číslo jednotky (nahrazuje 3 duplicity)
- Vrstva parcel v MapPicker - WMTS upgrade
- • **Nové tlačítko "Parcely"** v pravém horním rohu mapy
- • **ČÚZK WMTS vrstva** (nahrazuje WMS) - ostré rozlišení až do zoom 19
- • **REST endpoint**: `services.cuzk.cz/wmts/local-km-wmts-google/rest/WMTS/default/KN/{z}/{y}/{x}`
- • **Použití**: Pro snadnější identifikaci pozemku (garáže, pole, zahrady)
- • **Omezení**: Zobrazí se pouze při zoom 17+ (WMTS limit)
- • **UX**: Kliknutí zachová aktuální zoom (nepřibližuje pokud zoom ≥ 17)
- • **Marker**: Vysoký z-index (1000) aby byl viditelný nad vrstvou parcel
- • **Pouze pro prodávající**: Makléři stále vidí jen anonymní kruh
- Admin detail poptávky - rozšíření a editace
- • **Kompletní zobrazení všech polí** - LV, parcelní číslo, číslo jednotky, dispoziční řešení, vlastnictví, stav, typ budovy
- • **Editace katastrálních údajů** - parcelní číslo, LV, číslo jednotky (inline edit s uložením)
- • **Editace detailů nemovitosti** - dispozice, vlastnictví, stav, typ budovy
- • **Editace popisu** - inline editace textu poptávky
- • **Správa fotek**:
- • **Oprava duplicitní adresy** - sloučeno "Přesná adresa" a "Adresa" do jedné sekce
- UX vylepšení - Číslo jednotky
- • **Přesunuto pole "Číslo jednotky"** z Kroku 2 (Poloha) do Kroku 3 (Parametry)
- • **Lepší viditelnost** - pole bylo dříve schované na konci kroku Poloha
- • **Shrnutí** - číslo jednotky se nyní zobrazuje v přehledu (Krok 5)
- Oprava: LV jednotky se nyní správně získává
- • **Problém**: Když uživatel zadal číslo jednotky v Kroku 3, LV jednotky se nikdy nezískalo z ČÚZK API
- • **Příčina**: API se volalo pouze v Kroku 2 (pro parcelu), ne po zadání čísla jednotky
- • **Řešení**:
- • **Jak to funguje**:
- • **Pro domy**: LV se získá automaticky v Kroku 2 při výběru adresy (není potřeba číslo jednotky)
- KRITICKÁ OPRAVA: ČÚZK REST API KN autentizace
- • **Problém**: LV číslo se nikdy nezískávalo - API vracelo 401 Unauthorized
- • **Příčina**: Špatný název hlavičky `Api-Key` místo `ApiKey` (bez pomlčky)
- • **Dopad**: Všechny typy nemovitostí (byty, domy, pozemky) neměly LV
- • **Řešení**:
- • **Nyní funguje**: LV pro parcely i jednotky se správně získává
- Oprava TypeScript typů
- • **Regenerace Supabase typů** - oprava chyby s `DatabaseWithoutInternals`
- • **Nové type aliasy**: `Profile`, `Lead`, `LeadForBroker`, `Bid`, `Match`, `Network`
- • **Composite typy**: `BidWithLead`, `BidWithBroker`
- • **Enum typy**: `NotifyNewLeads`, `UserRole`
- • **Helper funkce**: `parseLeadParams()` pro bezpečné parsování JSONB params
- Oprava Changelog stránky pro produkci
- • **Problém**: Changelog stránka nefungovala na Coolify (špatná cesta k souboru)
- • **Řešení**:
- • **Funguje** v lokálním vývoji i na produkci
- Automatické získávání katastrálních dat z ČÚZK
- • **Parcelní číslo** - automaticky získáváno při výběru lokace
- • **List vlastnictví (LV)** - automaticky získáváno pro parcely i jednotky
- • **Číslo jednotky** - nové volitelné pole pro byty, kanceláře, garáže
- • **API `/api/cadastral-lookup`** rozšířeno:
- • **Databázové změny**:
- • **Environment variable**: `CUZK_API_KEY` pro REST API KN
- • **MapPicker komponenta** - rozšířena o nová pole z ČÚZK
- • **Formulář new-lead** - zobrazuje katastrální údaje v zeleném boxu
- Adresa kanceláře a příslušnost k síti
- • **Adresa kanceláře (působiště)** - nové povinné pole pro makléře
- • **Příslušnost k realitní síti** - nová funkcionalita
- • **Nová tabulka `networks`**
- • **Admin panel `/admin/networks`**
- • **Databázové změny**
- • **Frontend změny**
- Fakturační údaje makléře
- • **Sídlo firmy** - nové pole v registraci a profilu (readonly, načítá se z ARES)
- • **Bankovní účet** - volitelné pole pro makléře (formát: 123456789/0100)
- • **Databázová změna** - nové sloupce `company_address` a `bank_account` v tabulce `profiles`
- • **Registrace makléře** - sídlo firmy se zobrazí automaticky po ověření IČO
- • **Profil makléře** - sekce "Firemní údaje" rozšířena o sídlo a bankovní účet
- • **API `/api/ico-lookup`** - již vracelo adresu z ARES, nyní se využívá i ve frontendu
- Rozdělení jména na Jméno + Příjmení
- • **Databázová změna** - nové sloupce `first_name` a `last_name` v tabulce `profiles`
- • **Migrace dat** - automatické rozdělení `full_name` podle první mezery
- • **SQL funkce `create_match()`** - upravena pro snapshoty (spojuje first_name || ' ' || last_name)
- • **VIEW `admin_leads_summary`** - aktualizován pro spojené jméno
- • **14 frontend souborů upraveno**:
- • **Zpětná kompatibilita** - `full_name` zůstává jako deprecated, stále se ukládá pro fallback
- • **TypeScript typy** - aktualizovány + helper funkce `formatFullName()`
- Vylepšení ověření makléřů
- • **IČO ověření v profilu** - makléř musí ověřit IČO i při změně v profilu (stejně jako při registraci)
- • **VIES ověření plátcovství DPH** - integrace EU VAT Information Exchange System
- • **Readonly firemní údaje** - název firmy a DIČ nelze ručně upravovat
- UX vylepšení
- • **Odstranění duplicitního odkazu** - karta "Profil makléře" odstraněna z dashboardu makléře
- Technické změny
- • **API `/api/ico-lookup`** - rozšířeno o VIES ověření
- • **Profil stránka** - přidány stavy pro IČO ověření (`icoVerified`, `icoLoading`, `icoError`)
22. prosince 2025
- Migrace hostingu: Vercel → Hetzner + Coolify (DOKONČENO)
- • **Nový hosting**: Hetzner VPS (CPX32) + Coolify v Německu
- • **Důvod migrace**:
- • **Server specifikace**:
- • **Coolify**: Self-hosted PaaS (open-source Vercel alternativa)
- Vercel zrušen
- • **Projekt smazán** z Vercel dashboardu
- • **Supabase Auth** - aktualizovány redirect URLs na `spravnymakler.cz`
- • **Referer headery** - aktualizovány v API routes pro Mapy.cz
- • **GDPR dokumentace** - aktualizováno v privacy policy a registračních formulářích (Vercel → Hetzner)
- • **Admin settings** - Vercel Dashboard nahrazen Coolify Dashboard
- Aktualizované soubory
- • `api/address-suggest/route.ts` - Referer → spravnymakler.cz
- • `api/reverse-geocode/route.ts` - Referer → spravnymakler.cz
- • `privacy/page.tsx` - Hetzner místo Vercel
- • `admin/settings/page.tsx` - Coolify Dashboard
- • `auth/register/seller/page.tsx` - Hetzner v GDPR info
- • `auth/register/broker/page.tsx` - Hetzner v GDPR info
- Next.js konfigurace pro self-hosted
- • **Standalone output** - `output: "standalone"` v next.config.ts
- • **Vypnutá image optimizace** - `images.unoptimized: true` (řeší 400 Bad Request)
- Dokumentace
- • **MIGRATION-PLAN.md** - kompletní návod pro případnou budoucí migraci
- • Aktualizace CLAUDE.md, CHANGELOG.md, ROADMAP.md
19. prosince 2025
- PENB - Energetická třída budovy
- • **Nové pole při zadání poptávky** pro byt, dům a komerční nemovitosti
- • Klasifikace A–G s barevným kódováním podle české legislativy
- • Volitelné pole (prodávající nemusí vyplnit)
- • **Zobrazení na detail stránkách** (seller, broker, admin) - barevný badge s třídou
- • Nové konstanty: `ENERGY_RATINGS`, `ENERGY_RATING_CONFIG`, `ENERGY_RATING_LABELS`
- Vlastnická omezení nemovitosti
- • **Nové pole** pro zadání právních/finančních omezení nemovitosti
- • Radio výběr: "Bez omezení" / "Ano, má omezení"
- • **Kategorizované checkboxy** pro specifická omezení:
- • Implementováno v new-lead, edit stránce a všech detail stránkách
- • Zelený badge "Bez omezení" / Oranžový "Má omezení" + tagy specifikace
- • Nové konstanty: `PROPERTY_ENCUMBRANCES`, `ENCUMBRANCE_LABELS`
- • Nové typy: `has_encumbrances`, `encumbrances[]` v LeadParams
- UX vylepšení - pořadí polí ve formuláři
- • **Příslušenství před vlastnickými omezeními** - logičtější flow
- • Nejdřív fyzické vlastnosti, pak právní stav, pak popis
18. prosince 2025
- Pokročilé filtry pro makléře
- • **Nová komponenta `LocationFilter`** - hierarchický multi-select pro lokality
- • **Tabs filtr podle stavu nabídky** na `/broker/leads`
- • **Nové shadcn komponenty**: Popover, Checkbox, Collapsible
- Konsolidace kontextových souborů
- • **Nový `CLAUDE.md`** - jeden konsolidovaný kontextový soubor pro AI
- • **Cleanup** - staré duplicitní soubory odstraněny (zůstávají v git historii)
17. prosince 2025
- Rozšíření dat pro makléře
- • **View `leads_for_brokers`** - přidány chybějící sloupce:
- • **Migrace**: `20251217215805_add_missing_columns_to_leads_for_brokers_view.sql`
- Kompletně přepracovaný detail poptávky pro makléře
- • **Broker detail UI** (`/broker/leads/[id]`) - makléři nyní vidí všechny relevantní údaje:
- • **Lokace** - zobrazena jako samostatný blok s okresem a krajem
- • **Příslušenství** - kompletní seznam (balkón, lodžie, terasa, zahrada, sklep, parkování, garáž, výtah, bezbariérový, bazén)
- • **Vlastnosti domu** - nízkoenergetický, dřevostavba, přízemní/patrový, samostatný/řadový
- TypeScript typy
- • Aktualizované typy v `types.ts` pro `leads_for_brokers` view
- • Rozšířený `BrokerLead` type v broker detail stránce
10. prosince 2025
- Editace a zrušení poptávky (Seller)
- • **Nová stránka `/seller/leads/[id]/edit`** - editace poptávky
- • **Tlačítko "Zrušit poptávku"** na detailu
- • **Nový status `cancelled`** v LEAD_STATUS_CONFIG
- UX přeuspořádání detailu poptávky
- • **Nové pořadí sekcí** (podle priority pro uživatele):
- • **Rationale**: Seller primárně čeká na nabídky, detail už zná
- KRITICKÁ OPRAVA: leads_for_brokers VIEW
- • **Problém**: Makléři neviděli žádné poptávky (0 z 0)
- • **Příčina**: VIEW měl `security_invoker=true` - RLS na leads/bids blokoval dotazy
- • **Řešení**: Změna na `security_invoker=false` (SECURITY DEFINER)
- • VIEW nyní bypassuje RLS, bezpečnost zajištěna WHERE clause
- • Migrace: `fix_leads_for_brokers_security`
- GDPR a právní dokumenty
- • **Privacy Policy** (`/privacy`) - kompletní zásady ochrany osobních údajů
- • **Podmínky služby** (`/terms`) - obecné podmínky pro všechny uživatele
- Registrace - modální okna pro právní dokumenty
- • **Seller registrace** - Podmínky a Privacy se otevírají v modálním okně
- • **Broker registrace** - Privacy se otevírá v modálním okně
- Čestné prohlášení o neexkluzivitě
- • **Wizard krok 5** - nový checkbox před odesláním poptávky
- • **Databáze** - nové sloupce v `leads`:
- Detail poptávky pro prodávajícího
- • **Kompletní přehled zadaných údajů** - seller nyní vidí všechny informace které zadal
- Lightbox pro fotografie
- • **Nová komponenta `PhotoGallery`** - moderní prohlížení fotek
- • **Nová komponenta `PhotoLightbox`** - modal dialog pro zobrazení
- • **Použito na stránkách**:
- Oprava Leaflet mapy z-index
- • **Problém**: Mapa se zobrazovala nad lightbox overlayem
- • **Řešení**: Reset z-indexu pro `.leaflet-pane`, `.leaflet-control`, `.leaflet-control-zoom`
- • Opraveno v `LeadLocationMap` i `MapPicker` komponentách
- Broker lead detail - oprava načítání dat
- • Změna z `leads` tabulky na `leads_for_brokers` view
- • Správné RLS - broker vidí pouze povolená data
- Reset hesla
- • **Nová stránka `/auth/forgot-password`** - zadání emailu pro obnovení hesla
- • **Nová stránka `/auth/reset-password`** - zadání nového hesla
- • **Proxy aktualizován** - propouští reset-password a forgot-password bez Basic Auth
- Next.js 16 migrace
- • **Middleware → Proxy** - migrace na nový Next.js 16 systém
9. prosince 2025
- Oprava nahrávání fotek (PhotoUpload) - KRITICKÁ
- • **Bug #1**: Prohlížeč/mobil zamrzal při nahrávání fotek
- • **Bug #2**: Fotky se nenahrávaly (soubory neprošly validací)
- • **Bug #3**: Multiple GoTrueClient instances (stovky instancí)
- • **Vylepšení**:
- • **Nová závislost**: `heic2any`
- UX vylepšení pro makléře
- • **Odstranění "Lokalita pro makléře"** - pole odstraněno z formuláře i zobrazení
- • Makléři nyní vidí pouze mapu s anonymním kruhem a okres/kraj
- • Text u mapy změněn na "Nemovitost se nachází někde ve vyznačeném kruhu"
- Oprava mapování okres/kraj z Mapy.cz API
- • **Příčina problému**: V ČR jsou oba (okres i kraj) typu `regional.region`
- • **Opravené soubory**:
- • Nyní se data ukládají správně: `district` = okres, `region` = kraj
- Filtry pro makléře
- • **Dva filtry** na stránce `/broker/leads`:
- • Karty poptávek zobrazují "okres, kraj" pod názvem typu nemovitosti
- Formulář pro pozemky (krok 3)
- • **Nové pole "Kategorie pozemku"** (povinné):
- • **Přejmenování**: "Užitná plocha" → "Plocha pozemku" pro typ pozemek
- • **Zvýšený limit**: max 10 mil m² pro pozemky (vs 100 000 m² pro byty/domy)
- • **Odstraněny nesmyslné položky** pro pozemky:
- Databáze
- • **Nový sloupec**: `leads.land_category` (text)
- Nové konstanty
- • `LAND_CATEGORIES` v `lib/constants.ts`
8. prosince 2025
- Fix: Address suggest API - Referer header
- • **Problém**: Našeptávač adres vracel 403 "The client did not meet the restrictions"
- • **Příčina**: Mapy.cz API klíč má omezení na Referer header, server-side requesty neposílaly Referer
- • **Řešení**: Přidán `Referer: https://spravny-makler.vercel.app/` header do fetch requestu
- • **Doména REST API**: `api.mapy.com` (ne api.mapy.cz - ta funguje jen pro tiles)
- Mapy.cz - odstranění OpenStreetMap fallbacku
- • **address-suggest API route** - odstraněn Nominatim (OSM) fallback
- • **lead-location-map komponenta** - odstraněn OSM tiles fallback
- • Projekt nyní používá **výhradně Mapy.cz API**
- • **Nutné nastavit na Vercelu**:
- • **API klíč v Mapy.cz portálu** musí mít povolené domény včetně produkční
- Anonymní kruh pro makléře (Privacy Feature)
- • **Nový sloupec leads**: `display_latitude`, `display_longitude` - náhodně posunuté souřadnice
- • **Nová komponenta `LeadLocationMap`** - Leaflet mapa zobrazující 500m kruh
- • **Nová utility `lib/utils/geo.ts`** - funkce `generateRandomPointInCircle()`
- • **View `leads_for_brokers` aktualizována**:
- • Makléř vidí kruh o poloměru 500m, nemovitost je náhodně umístěna uvnitř (ne ve středu)
- • Offset se generuje jednou při vytvoření poptávky a nemění se
- Oprava Mapy.cz integrace
- • **Tiles URL**: `api.mapy.cz` pro mapové dlaždice
- • **REST API URL**: `api.mapy.com` pro suggest/geocode endpointy
- • **cityPart v lokaci**: zobrazuje se "Město - Část města" (např. "Plzeň - Plzeň 1")
- • Přidána proměnná `NEXT_PUBLIC_MAPY_CZ_API_KEY` pro client-side přístup
- KRITICKÁ OPRAVA #2: supabase-js deadlock bug
- • **Root cause**: supabase-js má bug kde `await` jakéhokoliv Supabase API volání uvnitř `onAuthStateChange` callbacku způsobí deadlock
- • **Symptom**: Po SMS ověření spinner točil navždy, Supabase query nikdy nedokončila (HTTP 200, ale Promise se neresolvla)
- • **Řešení**: `setTimeout(() => fetchProfile(...), 0)` - odloží volání mimo callback
- • **Reference**: https://github.com/orgs/supabase/discussions/19058
- KRITICKÁ OPRAVA #1: Supabase SSR caching bug
- • **Root cause**: `@supabase/ssr` `createBrowserClient` měl interní cache, která vracela stará data i po změnách v DB
- • **Symptom**: Po ověření emailu/SMS spinner stále točil, UI se neaktualizovalo
- • **Řešení**: Přechod z `@supabase/ssr` na standardní `@supabase/supabase-js`
- Změna závislostí
- • import { createBrowserClient } from '@supabase/ssr'
- Refaktor auth architektury (dříve)
- • **Singleton Supabase client** - jedna instance pro celou aplikaci (odstraněny race conditions)
- • **Nový useAuth hook** - centrální zdroj pravdy pro auth stav
- • **Smazán useUserRole hook** - nahrazen `useAuth().profile?.role`
- • **Odstraněna závislost na Realtime** - místo pasivního čekání na změny používáme explicitní invalidaci
- • **useAuthGuard zjednodušen** - používá useAuth() místo vlastního fetch
- Opravené problémy
- • Dashboard se nenačítal po registraci a verifikaci (stale cache)
- • Více Supabase klientů s různým stavem session
- • Komponenty nereagovaly na změny profilu po verifikaci
- Aktualizované soubory
- • `lib/supabase/client.ts` - změna z @supabase/ssr na @supabase/supabase-js
- • `AuthContext.tsx` - kompletní přepis (singleton + refetchProfile)
- • `useAuthGuard.ts` - zjednodušení (používá useAuth)
- • `role-indicator.tsx` - používá useAuth místo useUserRole
- • `hero-buttons.tsx` - používá useAuth místo useUserRole
- • `verify/page.tsx` - visibilitychange místo polling, router.replace()
7. prosince 2025
- Refaktor registračního procesu
- • **Jednotná verifikační stránka** `/auth/verify` - email + telefon na jednom místě
- • **Seller registrace opravena** - telefon povinný, "complete" krok s instrukcemi
- • **Broker registrace vyčištěna** - odstraněno ~150 řádků mrtvého OTP kódu
- • **Shared utility** `lib/utils/phone.ts` - normalizePhone funkce
- • **useAuthGuard** nyní vyžaduje ověření telefonu pro všechny uživatele (seller i broker)
- • Konzistentní flow: Registrace → Email potvrzení → Login → Verify telefon → Dashboard
- Mazání účtu s právní ochranou
- • **Soft delete s anonymizací** - účty se nemažou, data zůstávají pro právní účely
- • Edge Function `delete-account` s ověřením hesla
- • Dialog pro mazání účtu v profilu uživatele
- • **Audit log** - kompletní záloha dat před anonymizací (`account_deletion_audit`)
- • **Snapshot sloupce v matches** - jméno, telefon, IČO, firma makléře i prodávajícího
- • Anonymizace: `full_name` = "Smazaný uživatel", osobní údaje = null
- • Migrované RLS policies filtrují `deleted_at IS NULL`
- Bezpečnostní opravy
- • **SECURITY INVOKER** - view `leads_for_brokers` nyní respektuje volajícího
- • **SET search_path=public** - funkce `create_match` chráněna proti injection
- • Memory leak fix v broker registraci (interval cleanup)
- Optimalizace výkonu
- • **Database views** pro admin panel:
- • **Pagination** - limit 100 záznamů na admin stránkách
- • **useMemo** - memoizace filtrovaných seznamů (users, bids, matches)
- • **Lazy loading** - `browser-image-compression` načítáno dynamicky
- • **Next.js Image** - remotePatterns pro Supabase Storage
- Opravy
- • Fix: Vonage SMS credentials - aktualizace API klíčů
- • Fix: Sender ID zkráceno na max 11 znaků (limit Vonage)
6. prosince 2025
- Code Cleanup & Performance
- • **RLS Performance Fix** - `auth.uid()` → `(select auth.uid())` v 15 policies
- • **Nová komponenta `LoadingSpinner`** - nahrazuje 7 duplicitních spinnerů
- • Odstraněn debug `console.log` v photo-upload.tsx
- • Migrace: `fix_rls_policies_performance`
- Admin panel
- • Admin rozhraní s kompletní správou systému
- • Dashboard se statistikami (uživatelé, poptávky, nabídky, matche)
- • Seznam uživatelů s filtrováním podle role
- • Detail uživatele se změnou role a možností smazání
- • Seznam poptávek s plnými adresami
- • Detail poptávky se změnou statusu a možností smazání
- • Správa nabídek (změna statusu, mazání) přímo v detailu poptávky
- • Stránka spárování s kontaktními údaji obou stran
- • Nastavení systému s přehledem služeb
- Email notifikace
- • Resend integrace - doména ověřena (SPF, DKIM, MX)
- • Edge Function `send-notification-email`
- • Supabase Vault secrets (supabase_url, service_role_key)
- • Edge Function secrets (RESEND_API_KEY, FROM_EMAIL, SITE_URL)
- • Potvrzení emailu při registraci zapnuto
- Registrace makléře - právní compliance
- • **Explicitní souhlas s podmínkami** - viditelné klíčové body (20% provize, zákaz obcházení, mlčenlivost)
- • **Modal s celým zněním smlouvy** - ScrollArea pro přehledné čtení
- • **SMS ověření telefonu** - 6místný OTP kód přes Vonage
- • Normalizace telefonního čísla (+420)
- • Audit log souhlasu (verze, timestamp, body)
- • DB sloupce: `terms_accepted_at`, `terms_version`, `terms_explicit_points`, `phone_verified_at`, `ico`
- IČO ověření a kontrola živnosti
- • **IČO je povinné** pro registraci makléře
- • **ARES API integrace** - ověření firmy v registru ARES
- • API route `/api/ico-lookup` - lookup + RŽP živnosti
- • Validace kontrolní číslice IČO (modulo 11)
- • **Auto-fill názvu firmy** po ověření IČO
- • **Kontrola živnosti** "Realitní zprostředkování" z RŽP
- • Chybějící živnost = varování (neblokuje registraci)
- • DB sloupec: `has_realitni_zivnost` (boolean)
- • Admin může manuálně upravit stav živnosti
- Opravy
- • Fix: `commission` → `commission_percent` v admin detailech
- • RLS policies pro admin přístup (SELECT, UPDATE, DELETE)
- • Fix: useUserRole hook - automatický update UI při přihlášení/odhlášení
- • Fix: chybějící závislost @radix-ui/react-alert-dialog
5. prosince 2025
- Nové funkce
- • **Location Picker** - výběr polohy na mapě pro pozemky/garáže bez adresy
- • **Katastrální údaje** - automatické získání k.ú. z ČÚZK RUIAN API
- • API route `/api/cadastral-lookup` pro katastrální území
- • API route `/api/reverse-geocode` pro Mapy.cz reverse geocoding
- • Přepínač "Mám adresu" / "Vybrat na mapě" ve wizardu
- Změny
- • DB: street je nullable - pozemky/garáže mohou být bez adresy
- • AddressAutocomplete vrací i souřadnice (lat/lon)
- • Wizard step 2 přepracován s přepínačem metody zadání lokace
4. prosince 2025
- Základní funkce
- • Registrace prodávající / makléř
- • Wizard pro zadání nemovitosti (5 kroků)
- • Fotky interiér/exteriér s kompresí
- • Address autocomplete (Mapy.cz)
- • Seznam poptávek pro makléře
- • Odeslání nabídky
- • Match systém
- • Dashboard pro oba typy uživatelů
- • **Logo** v hlavičce a na homepage
- • **useAuthGuard hook** - centralizovaná autentizace
- • **useUserRole hook** - získání role uživatele
- • **LeadCard komponenta** - reusable karta pro poptávky
- • Changelog stránka pro testery
- • Formátování cen s mezerami (1 000 000 Kč)
- • Dynamická homepage podle role uživatele
- • Password protection pro staging
- • **Role indikátor** - barevný pruh (Prodávající/Makléř/Admin)
- • **Stránka profilu** (`/profile`)
- • **Filtry podle kraje/okresu** na `/broker/leads`
- • **Preference makléře** - výběr krajů a frekvence notifikací
- • Extrakce kraje a okresu z Mapy.cz API
- Opravy
- • RLS recursion při vytváření nabídky
- • Hydration error na homepage
- • Doplněny kraje/okresy u existujících poptávek
- • Security issues a useEffect dependencies
- • N+1 query optimalizace
Typy email notifikací