diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c71a078..39e39d3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,8 @@ import { Fragment, type ChangeEvent as ReactChangeEvent, type ClipboardEvent as ReactClipboardEvent, type Dispatch, type DragEvent as ReactDragEvent, type FormEvent as ReactFormEvent, type KeyboardEvent as ReactKeyboardEvent, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type ReactNode, type SetStateAction, type WheelEvent as ReactWheelEvent, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import './styles.css'; +import { Link, NavLink, Navigate, Outlet, Route, Routes, useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom'; +import { ROUTES, listingPath, mapPath, negotiationPath, adminTabPath, ADMIN_PATH_TO_TAB, type AdminTabKey } from './routes'; +import { ProtectedRoute } from './ProtectedRoute'; import { apiFetch, useAuth } from './auth'; import type { AuthUser, ContactPreference, PreferredLanguage } from './auth'; import { useNotifications, toDisplayNotifications } from './notifications'; @@ -12,7 +15,8 @@ const cityImage = new URL('./assets/city-panorama.png', import.meta.url).href; const loginRoomImage = new URL('./assets/login-room.png', import.meta.url).href; const SMS_VERIFICATION_CODE = '123456'; -type View = 'home' | 'buy' | 'rent' | 'sell' | 'valuation' | 'priceHistory' | 'districtRanking' | 'negotiation' | 'comparison' | 'add' | 'services' | 'guides' | 'guideBuying' | 'creditCalculator' | 'map' | 'login' | 'account' | 'accountSearches' | 'accountPriceAlerts' | 'accountMeetings' | 'accountListings' | 'accountSettings' | 'accountSecurity' | 'accountProfileEdit' | 'accountPublicProfile' | 'accountHelpContact' | 'notifications' | 'favorites' | 'messages' | 'admin' | 'listingDetail'; +// Podswietlenie aktywnej pozycji menu wyznacza NavLink na podstawie adresu. +const navLinkClass = ({ isActive }: { isActive: boolean }) => (isActive ? 'active' : ''); type MessageThreadListing = { listingId: number; @@ -113,11 +117,6 @@ type LiveListingSummary = ApiListingSummary & { const LIVE_LISTINGS_REFRESH_MS = 15000; -const PROTECTED_VIEWS = new Set([ - 'add', 'admin', 'account', 'accountSearches', 'accountPriceAlerts', 'accountMeetings', 'accountListings', 'accountSettings', - 'accountSecurity', 'accountProfileEdit', 'accountPublicProfile', 'accountHelpContact', 'notifications', 'favorites', 'messages', 'comparison', -]); - type NotificationCategory = 'listing' | 'messages' | 'system'; type UserNotification = { @@ -703,28 +702,28 @@ const insightCards = [ title: 'AI wycena mieszkania', text: 'Sprawdź ile warte jest Twoje mieszkanie w 2 minuty', action: 'Wycena za darmo', - view: 'valuation' as const, + path: ROUTES.valuation, }, { icon: 'trend', title: 'Historia cen', text: 'Sprawdź jak zmieniały się ceny w okolicy', action: 'Zobacz wykresy', - view: 'priceHistory' as const, + path: ROUTES.priceHistory, }, { icon: 'house', title: 'Ranking dzielnic', text: 'Porównaj dzielnice pod kątem życia i inwestycji', action: 'Zobacz ranking', - view: 'districtRanking' as const, + path: ROUTES.districtRanking, }, { icon: 'coin', title: 'Ile możesz utargować?', text: 'AI podpowie realistyczną cenę transakcyjną', action: 'Sprawdź teraz', - view: 'negotiation' as const, + path: ROUTES.negotiation, }, ]; @@ -2618,12 +2617,8 @@ function preferredLanguageLabel(language: PreferredLanguage | null | undefined): function App() { const { user, logout } = useAuth(); - const [view, setView] = useState(() => (window.location.hash === '#login' ? 'login' : 'home')); - const [mapCity, setMapCity] = useState(''); - const [mapOfferType, setMapOfferType] = useState('SALE'); - const [selectedNegotiationOffer, setSelectedNegotiationOffer] = useState(buyListings[0] ?? null); - const [selectedListingId, setSelectedListingId] = useState(null); - const [messageThreadTarget, setMessageThreadTarget] = useState(null); + const navigate = useNavigate(); + const location = useLocation(); const [favoriteListingsState, setFavoriteListingsState] = useState(favoriteListings); const [favoritesPreferredTab, setFavoritesPreferredTab] = useState('buy'); const [listingPriceAlertSubscriptionsState, setListingPriceAlertSubscriptionsState] = useState([]); @@ -2653,41 +2648,17 @@ function App() { const notificationsCenter = useNotifications(); const [savedSearchesState, setSavedSearchesState] = useState([]); - const clearLoginHash = () => { - if (window.location.hash === '#login') { - window.history.replaceState(null, '', `${window.location.pathname}${window.location.search}`); - } - }; - - // Nawigacja z bramka autoryzacji: widoki chronione wymagaja zalogowania, - // panel admina wymaga roli ADMIN. - const navigate = (nextView: View) => { - let target = nextView; - if (target === 'admin' && user?.role !== 'ADMIN') { - target = user ? 'account' : 'login'; - } else if (PROTECTED_VIEWS.has(target) && !user) { - target = 'login'; - } - - setView(target); - if (target === 'login') { - window.location.hash = 'login'; - return; - } - clearLoginHash(); - }; - - // Po udanym logowaniu kierujemy admina do panelu, uzytkownika do konta - // (pomijamy bramke navigate, bo stan user moze byc jeszcze swiezo ustawiany). + // Po udanym logowaniu wracamy tam, gdzie uzytkownik zmierzal (ProtectedRoute zapisuje cel + // w location.state.from). Gdy wszedl na logowanie wprost - admin do panelu, uzytkownik do konta. const handleAuthenticated = (loggedUser: AuthUser) => { - setView(loggedUser.role === 'ADMIN' ? 'admin' : 'account'); - clearLoginHash(); + const from = (location.state as { from?: string } | null)?.from; + const fallback = loggedUser.role === 'ADMIN' ? ROUTES.admin : ROUTES.account; + navigate(from && from !== ROUTES.login ? from : fallback, { replace: true }); }; const handleLogout = () => { logout(); - setView('home'); - clearLoginHash(); + navigate(ROUTES.home); }; // Z kalkulatora zdolności: ustaw maksymalną cenę ofert na obliczony budżet i przejdź do wyszukiwarki. @@ -2698,20 +2669,9 @@ function App() { filters.setPriceMin(''); filters.setPriceMax(String(Math.round(maxPrice))); } - navigate('buy'); + navigate(ROUTES.buy); }; - useEffect(() => { - const onHashChange = () => { - if (window.location.hash === '#login') { - setView('login'); - } - }; - - window.addEventListener('hashchange', onHashChange); - return () => window.removeEventListener('hashchange', onHashChange); - }, []); - useEffect(() => { if (!user) { setSavedSearchesState([]); @@ -2832,10 +2792,10 @@ function App() { }, [user]); useEffect(() => { - if (view === 'favorites') { + if (location.pathname === ROUTES.favorites) { setUnreadFavoriteIds(new Set()); } - }, [view]); + }, [location.pathname]); useEffect(() => { const preferred = user?.preferredLanguage ?? 'PL'; @@ -3033,25 +2993,25 @@ function App() { translationTimerRef.current = null; } }; - }, [user?.preferredLanguage, view]); + }, [user?.preferredLanguage, location.pathname]); + // Miasto i typ oferty trafiaja do query stringa - wynik na mapie da sie udostepnic linkiem. const openMap = (name: string, source: 'buy' | 'rent' = 'buy') => { - setMapCity(name); - setMapOfferType(source === 'rent' ? 'RENT' : 'SALE'); - navigate('map'); + navigate(mapPath(name, source === 'rent' ? 'RENT' : 'SALE')); }; + + // Wybrana oferta to dane przejsciowe miedzy trasami - przekazujemy je w location.state, + // dzieki czemu nie zostaja w globalnym stanie po opuszczeniu widoku. const openNegotiation = (offer: BuyOffer) => { - setSelectedNegotiationOffer(offer); - navigate('negotiation'); + navigate(negotiationPath(), { state: { offer } }); }; + const openListing = (id: number) => { - setSelectedListingId(id); - navigate('listingDetail'); + navigate(listingPath(id)); }; const openMessagesWithTarget = (target: MessageThreadTarget) => { - setMessageThreadTarget(target); - navigate('messages'); + navigate(ROUTES.messages, { state: { threadTarget: target } }); }; // Klik w powiadomienie: oznacz jako przeczytane (znika z dzwonka, licznik -1) i przejdź do właściwego miejsca. @@ -3059,7 +3019,7 @@ function App() { notificationsCenter.markRead(notification.id); const link = notification.link; if (!link) { - navigate('notifications'); + navigate(ROUTES.notifications); return; } if (link.startsWith('listing:')) { @@ -3070,35 +3030,35 @@ function App() { } } if (link.startsWith('messages')) { - navigate('messages'); + navigate(ROUTES.messages); return; } if (link === 'priceAlerts') { - navigate('accountPriceAlerts'); + navigate(ROUTES.accountPriceAlerts); return; } if (link === 'meetings') { - navigate('accountMeetings'); + navigate(ROUTES.accountMeetings); return; } if (link === 'savedSearches') { - navigate('accountSearches'); + navigate(ROUTES.accountSearches); return; } if (link === 'favorites') { - navigate('favorites'); + navigate(ROUTES.favorites); return; } if (link === 'accountSecurity' || link === 'account' || link === 'accountListings') { - navigate(link as View); + navigate(ROUTES[link]); return; } - navigate('notifications'); + navigate(ROUTES.notifications); }; const toggleSavedSearch = (mode: SavedSearchMode) => { if (!user) { - navigate('login'); + navigate(ROUTES.login); return; } @@ -3121,7 +3081,7 @@ function App() { const openSavedSearch = (savedSearch: SavedSearchRecord) => { applySavedSearchToFilters(filters, savedSearch); - navigate(savedSearch.mode === 'rent' ? 'rent' : 'buy'); + navigate(savedSearch.mode === 'rent' ? ROUTES.rent : ROUTES.buy); }; const removeSavedSearch = (savedSearchId: string) => { @@ -3406,107 +3366,177 @@ function App() { }, [user, monitoredListingIdList, monitoredListingIdsSignature, listingPriceAlertIdList, listingPriceAlertIdsSignature]); return ( -
-
0} onOpenNotification={handleOpenNotification} /> +
+
0} onOpenNotification={handleOpenNotification} />
- {view === 'login' && } - {view === 'admin' && } - {view === 'account' && } - {view === 'accountSearches' && ( - + {/* Trasy publiczne */} + } /> + } /> + toggleSavedSearch('buy')} + /> + )} /> - )} - {view === 'accountPriceAlerts' && } - {view === 'comparison' && } - {view === 'accountMeetings' && } - {view === 'accountListings' && } - {view === 'accountSettings' && } - {view === 'accountSecurity' && } - {view === 'accountProfileEdit' && } - {view === 'accountPublicProfile' && } - {view === 'accountHelpContact' && } - {view === 'notifications' && } - {view === 'messages' && ( - setMessageThreadTarget(null)} + toggleSavedSearch('rent')} + /> + )} /> - )} - {view === 'favorites' && ( - + )} /> - )} - {view === 'buy' && ( - toggleSavedSearch('buy')} - /> - )} - {view === 'rent' && ( - toggleSavedSearch('rent')} - /> - )} - {view === 'sell' && } - {view === 'valuation' && } - {view === 'creditCalculator' && } - {view === 'priceHistory' && } - {view === 'districtRanking' && } - {view === 'negotiation' && ( - - )} - {view === 'add' && } - {view === 'listingDetail' && ( - - )} - {view === 'services' && } - {view === 'guides' && } - {view === 'guideBuying' && } - {view === 'map' && } - {view === 'home' && } + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + {/* Trasy wymagajace zalogowania */} + }> + } /> + + )} + /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + )} + /> + + + {/* Panel administratora - zakladki jako podsciezki /admin/* */} + }> + } /> + + + } /> +
); } +// Szczegoly oferty: identyfikator pochodzi z adresu (/oferta/:id), nie ze stanu Reacta. +// Dzieki temu link do oferty da sie wyslac, odswiezyc i otworzyc w nowej karcie. +function ListingDetailRoute(props: { + onStartChat: (target: MessageThreadTarget) => void; + isFavorite: (listingId: number) => boolean; + onToggleFavorite: (listing: ApiListingDetail) => void; + isListingAlertEnabled: (listingId: number) => boolean; + onToggleListingAlert: (listing: ApiListingDetail) => void; +}) { + const { id } = useParams<{ id: string }>(); + const listingId = Number(id); + + if (!Number.isFinite(listingId) || listingId <= 0) { + return ; + } + + return ; +} + +// Mapa czyta miasto i typ oferty z query stringa (/mapa?miasto=Krakow&typ=wynajem). +function MapSearchRoute() { + const [searchParams] = useSearchParams(); + const city = searchParams.get('miasto') ?? ''; + const offerType: ApiOfferType = searchParams.get('typ') === 'wynajem' ? 'RENT' : 'SALE'; + + return ; +} + +// Negocjacje: oferta wskazana z listy ofert przychodzi w location.state (dane przejsciowe), +// wybor z rozwijanej listy to juz zwykly stan formularza. +function NegotiationRoute() { + const location = useLocation(); + const offerFromState = (location.state as { offer?: BuyOffer } | null)?.offer ?? null; + const [selectedOffer, setSelectedOffer] = useState(offerFromState ?? buyListings[0] ?? null); + + useEffect(() => { + if (offerFromState) { + setSelectedOffer(offerFromState); + } + }, [offerFromState]); + + return ; +} + +// Nieznany adres: czytelny komunikat zamiast pustego ekranu. +function NotFoundPage() { + return ( +
+
+

404

+

Nie znaleźliśmy tej strony

+

Adres jest nieprawidłowy albo strona została przeniesiona.

+
+ Wróć na stronę główną + Przeglądaj oferty +
+
+
+ ); +} + type AdminListing = { id: number; title: string; @@ -3815,7 +3845,8 @@ function AdminMessageModal({ user, onClose }: { user: AuthUser; onClose: () => v ); } -function AdminPage({ onNavigate }: { onNavigate: (view: View) => void }) { +function AdminPage() { + const navigate = useNavigate(); const { user } = useAuth(); const [tab, setTab] = useState('dashboard'); const [users, setUsers] = useState([]); @@ -4204,7 +4235,7 @@ function AdminPage({ onNavigate }: { onNavigate: (view: View) => void }) {
- @@ -4323,7 +4354,7 @@ function AdminPage({ onNavigate }: { onNavigate: (view: View) => void }) {

Szybkie akcje

- + @@ -5045,7 +5076,8 @@ function AdminPage({ onNavigate }: { onNavigate: (view: View) => void }) { ); } -function DistrictRankingPage({ onNavigate }: { onNavigate: (view: View) => void }) { +function DistrictRankingPage() { + const navigate = useNavigate(); const [rankingMetric, setRankingMetric] = useState('overall'); const [propertyType, setPropertyType] = useState('flat'); const [marketType, setMarketType] = useState('all'); @@ -5096,9 +5128,9 @@ function DistrictRankingPage({ onNavigate }: { onNavigate: (view: View) => void
- + - +
@@ -5210,16 +5242,15 @@ function DistrictRankingPage({ onNavigate }: { onNavigate: (view: View) => void } function NegotiationPage({ - onNavigate, offers, selectedOffer, onSelectOffer, }: { - onNavigate: (view: View) => void; offers: BuyOffer[]; selectedOffer: BuyOffer | null; onSelectOffer: (offer: BuyOffer) => void; }) { + const navigate = useNavigate(); const fallbackOffer = offers[0] ?? null; const activeOffer = selectedOffer ?? fallbackOffer; @@ -5228,9 +5259,9 @@ function NegotiationPage({
- + - +
@@ -5265,9 +5296,9 @@ function NegotiationPage({
- + - +
@@ -5370,7 +5401,7 @@ function NegotiationPage({

Nasi eksperci pomogą Ci przejść przez proces zakupu i wynegocjować najlepszą cenę.

- + Pierwsza konsultacja gratis
@@ -5424,23 +5455,13 @@ function NegotiationPage({ ); } -function AccountSidebar({ onNavigate, activeView }: { onNavigate: (view: View) => void; activeView: View }) { +function AccountSidebar() { const { user } = useAuth(); - const isSettingsView = activeView === 'accountSettings' || activeView === 'accountProfileEdit' || activeView === 'accountPublicProfile'; - const isSecurityView = activeView === 'accountSecurity'; - const isHelpContactView = activeView === 'accountHelpContact'; - const isDashboardView = activeView === 'account'; - const isSearchesView = activeView === 'accountSearches'; - const isFavoritesView = activeView === 'favorites'; - const isPriceAlertsView = activeView === 'accountPriceAlerts'; - const isMeetingsView = activeView === 'accountMeetings'; - const isListingsView = activeView === 'accountListings'; - const isPriceHistoryView = activeView === 'priceHistory'; - const isNegotiationView = activeView === 'negotiation'; - const isValuationView = activeView === 'valuation'; - const isCreditCalculatorView = activeView === 'creditCalculator'; - const isDistrictRankingView = activeView === 'districtRanking'; - const isComparisonView = activeView === 'comparison'; + const location = useLocation(); + // Ustawienia obejmuja takze podstrony profilu - podswietlamy je wspolna pozycja menu. + const isSettingsSection = location.pathname === ROUTES.accountSettings + || location.pathname === ROUTES.accountProfileEdit + || location.pathname === ROUTES.accountPublicProfile; const [messagesUnreadCount, setMessagesUnreadCount] = useState(0); const allListingsCount = (() => { if (typeof window === 'undefined') { @@ -5515,35 +5536,35 @@ function AccountSidebar({ onNavigate, activeView }: { onNavigate: (view: View) =

Moje aktywności

- - - - - + Panel główny + Zapisane wyszukiwania {savedSearchCount} + Ulubione ogłoszenia {favoritesCount} + Alerty cenowe {priceAlertsCount} + Porównanie mieszkań

Moje transakcje

- - - + Zapytania i wiadomości {messagesUnreadCount} + Umówione spotkania + Moje oferty {allListingsCount}

Moje narzędzia

- - - - - + Zdolność kredytowa + Historia cen + Ile możesz utargować? + Wyceny mieszkań + Ranking dzielnic

Ustawienia

- - - - + Dane i ustawienia + Powiadomienia + Bezpieczeństwo + Pomoc i kontakt
@@ -5558,16 +5579,13 @@ function AccountSidebar({ onNavigate, activeView }: { onNavigate: (view: View) = } function AccountDashboardPage({ - onNavigate, - activeView, savedSearches, onOpenSavedSearch, }: { - onNavigate: (view: View) => void; - activeView: View; savedSearches: SavedSearchRecord[]; onOpenSavedSearch: (savedSearch: SavedSearchRecord) => void; }) { + const navigate = useNavigate(); const { user } = useAuth(); type DashboardWidgetId = 'recent' | 'alerts' | 'searches' | 'tools'; type DashboardWidgetConfig = { @@ -5594,12 +5612,12 @@ function AccountDashboardPage({ const [activeAlerts, setActiveAlerts] = useState(() => alerts.map(() => true)); const tools = [ - { icon: 'money', title: 'Zdolność kredytowa', text: 'Sprawdź, na jaki kredyt Cię stać i jaka będzie rata', view: 'creditCalculator' as View }, - { icon: 'chart', title: 'Historia cen', text: 'Sprawdź jak zmieniały się ceny mieszkań w czasie', view: 'priceHistory' as View }, - { icon: 'coin', title: 'Ile możesz utargować?', text: 'AI podpowie realistyczną cenę transakcyjną', view: 'negotiation' as View }, - { icon: 'calculator', title: 'Wycena mieszkania', text: 'Darmowa wycena Twojego mieszkania online', view: 'valuation' as View }, - { icon: 'house', title: 'Ranking dzielnic', text: 'Zobacz najlepsze dzielnice w Polsce', view: 'districtRanking' as View }, - { icon: 'shuffle', title: 'Porównanie mieszkań', text: 'Porównuj oferty i wybieraj najlepsze', view: 'comparison' as View }, + { icon: 'money', title: 'Zdolność kredytowa', text: 'Sprawdź, na jaki kredyt Cię stać i jaka będzie rata', path: ROUTES.creditCalculator }, + { icon: 'chart', title: 'Historia cen', text: 'Sprawdź jak zmieniały się ceny mieszkań w czasie', path: ROUTES.priceHistory }, + { icon: 'coin', title: 'Ile możesz utargować?', text: 'AI podpowie realistyczną cenę transakcyjną', path: ROUTES.negotiation }, + { icon: 'calculator', title: 'Wycena mieszkania', text: 'Darmowa wycena Twojego mieszkania online', path: ROUTES.valuation }, + { icon: 'house', title: 'Ranking dzielnic', text: 'Zobacz najlepsze dzielnice w Polsce', path: ROUTES.districtRanking }, + { icon: 'shuffle', title: 'Porównanie mieszkań', text: 'Porównuj oferty i wybieraj najlepsze', path: ROUTES.comparison }, ]; const recentListings = [ @@ -5794,7 +5812,7 @@ function AccountDashboardPage({

Moje wyszukiwania

- +
{savedSearches.slice(0, 4).map((row) => ( @@ -5821,7 +5839,7 @@ function AccountDashboardPage({ ))} {savedSearches.length === 0 &&

Nie masz jeszcze zapisanych wyszukiwań. Wejdź w Kup lub Wynajem i kliknij dzwonek „Zapisz wyszukiwanie”.

} - +
), @@ -5836,11 +5854,11 @@ function AccountDashboardPage({ key={tool.title} role="button" tabIndex={0} - onClick={() => onNavigate(tool.view)} + onClick={() => navigate(tool.path)} onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); - onNavigate(tool.view); + navigate(tool.path); } }} > @@ -5860,7 +5878,7 @@ function AccountDashboardPage({ return (
- +
@@ -6019,18 +6037,15 @@ function AccountDashboardPage({ } function AccountSavedSearchesPage({ - onNavigate, - activeView, savedSearches, onOpenSavedSearch, onRemoveSavedSearch, }: { - onNavigate: (view: View) => void; - activeView: View; savedSearches: SavedSearchRecord[]; onOpenSavedSearch: (savedSearch: SavedSearchRecord) => void; onRemoveSavedSearch: (savedSearchId: string) => void; }) { + const navigate = useNavigate(); const [query, setQuery] = useState(''); const [modeFilter, setModeFilter] = useState<'all' | SavedSearchMode>('all'); const [sortMode, setSortMode] = useState<'newest' | 'oldest' | 'city'>('newest'); @@ -6066,7 +6081,7 @@ function AccountSavedSearchesPage({ return (
- +
@@ -6075,7 +6090,7 @@ function AccountSavedSearchesPage({

Zapisane wyszukiwania

Wróć do swoich filtrów jednym kliknięciem i przejdź od razu do odpowiedniej listy ofert.

- +
@@ -6171,7 +6186,7 @@ function AccountSavedSearchesPage({
Nie masz jeszcze zapisanych wyszukiwań

Ustaw filtry w Kup lub Wynajem i kliknij dzwonek „Zapisz wyszukiwanie”, a pojawią się tutaj.

-
@@ -6202,12 +6217,8 @@ function AccountSavedSearchesPage({ } function AccountHelpContactPage({ - onNavigate, - activeView, user, }: { - onNavigate: (view: View) => void; - activeView: View; user: AuthUser | null; }) { type HelpContactFormValues = { @@ -6425,7 +6436,7 @@ function AccountHelpContactPage({ return (
- +
@@ -6554,16 +6565,13 @@ function AccountHelpContactPage({ } function AccountPriceAlertsPage({ - onNavigate, - activeView, user, onOpenListing, }: { - onNavigate: (view: View) => void; - activeView: View; user: AuthUser | null; onOpenListing: (id: number) => void; }) { + const navigate = useNavigate(); const [cityAlerts, setCityAlerts] = useState([]); const [priceEvents, setPriceEvents] = useState([]); const [cityPriceEvents, setCityPriceEvents] = useState([]); @@ -6662,7 +6670,7 @@ function AccountPriceAlertsPage({ return (
- +
@@ -6758,7 +6766,7 @@ function AccountPriceAlertsPage({ onOpenListing(activeListingEvent.listingId); return; } - onNavigate('priceHistory'); + navigate(ROUTES.priceHistory); }} disabled={!latestEventType} > @@ -6813,7 +6821,7 @@ function AccountPriceAlertsPage({
)}
- +
@@ -6830,7 +6838,7 @@ function AccountPriceAlertsPage({ Usuń )) : ( - )}
- +
@@ -6848,7 +6856,8 @@ function AccountPriceAlertsPage({ ); } -function AccountComparisonPage({ onNavigate, activeView, onOpenListing }: { onNavigate: (view: View) => void; activeView: View; onOpenListing: (id: number) => void }) { +function AccountComparisonPage({ onOpenListing }: { onOpenListing: (id: number) => void }) { + const navigate = useNavigate(); type ComparisonPropertyFilter = 'ALL' | ApiPropertyType | 'LAND' | 'COMMERCIAL' | 'ROOM'; const comparisonPropertyOptions: Array<{ value: ComparisonPropertyFilter; label: string }> = [ @@ -7259,13 +7268,13 @@ function AccountComparisonPage({ onNavigate, activeView, onOpenListing }: { onNa return (
- +
- + - +
@@ -7449,7 +7458,7 @@ function AccountComparisonPage({ onNavigate, activeView, onOpenListing }: { onNa ); } -function AccountMeetingsPage({ onNavigate, activeView }: { onNavigate: (view: View) => void; activeView: View }) { +function AccountMeetingsPage() { const { notify } = useNotifications(); type MeetingType = 'buy' | 'rent' | 'own'; type MeetingStatus = 'planned' | 'completed' | 'cancelled'; @@ -7846,7 +7855,7 @@ function AccountMeetingsPage({ onNavigate, activeView }: { onNavigate: (view: Vi return (
- +
@@ -8055,7 +8064,8 @@ function MyApiListingsBand({ onOpenListing }: { onOpenListing: (id: number) => v ); } -function AccountListingsPage({ onNavigate, activeView, onOpenListing }: { onNavigate: (view: View) => void; activeView: View; onOpenListing: (id: number) => void }) { +function AccountListingsPage({ onOpenListing }: { onOpenListing: (id: number) => void }) { + const navigate = useNavigate(); type ListingCategory = 'sale' | 'rent'; type ListingStatus = 'active' | 'paused' | 'archived'; type ListingItem = { @@ -8538,7 +8548,7 @@ function AccountListingsPage({ onNavigate, activeView, onOpenListing }: { onNavi }; window.localStorage.setItem(EDIT_LISTING_DRAFT_STORAGE_KEY, JSON.stringify(draft)); - onNavigate('add'); + navigate(ROUTES.add); }; const togglePause = (id: string) => { @@ -8657,7 +8667,7 @@ function AccountListingsPage({ onNavigate, activeView, onOpenListing }: { onNavi return (
- +
@@ -8814,7 +8824,8 @@ function AccountListingsPage({ onNavigate, activeView, onOpenListing }: { onNavi ); } -function AccountSettingsPage({ onNavigate, activeView }: { onNavigate: (view: View) => void; activeView: View }) { +function AccountSettingsPage() { + const navigate = useNavigate(); const { user } = useAuth(); const [accountSwitches, setAccountSwitches] = useState([true, true, false, false]); // Indeks 4 ("Oferty promowane i newsletter") to zgoda marketingowa e-mail zapisywana na serwerze. @@ -8868,7 +8879,7 @@ function AccountSettingsPage({ onNavigate, activeView }: { onNavigate: (view: Vi return (
- +
@@ -8887,7 +8898,7 @@ function AccountSettingsPage({ onNavigate, activeView }: { onNavigate: (view: Vi {getDisplayName(user)} Konto Premium
- +
Adres e-mail{user?.email}
@@ -8979,7 +8990,7 @@ function AccountSettingsPage({ onNavigate, activeView }: { onNavigate: (view: Vi ); } -function AccountSecurityPage({ onNavigate, activeView }: { onNavigate: (view: View) => void; activeView: View }) { +function AccountSecurityPage() { const { user } = useAuth(); const recentSecurityActivity = [ { @@ -9086,7 +9097,7 @@ function AccountSecurityPage({ onNavigate, activeView }: { onNavigate: (view: Vi return (
- +
@@ -9166,7 +9177,8 @@ function AccountSecurityPage({ onNavigate, activeView }: { onNavigate: (view: Vi ); } -function AccountProfileEditPage({ onNavigate, activeView }: { onNavigate: (view: View) => void; activeView: View }) { +function AccountProfileEditPage() { + const navigate = useNavigate(); const { user, updateProfile } = useAuth(); const { notify } = useNotifications(); type UploadTarget = 'avatar' | 'cover'; @@ -9630,7 +9642,7 @@ function AccountProfileEditPage({ onNavigate, activeView }: { onNavigate: (view: <>
- +
Dane i ustawienia Edytuj profil
@@ -9824,7 +9836,7 @@ function AccountProfileEditPage({ onNavigate, activeView }: { onNavigate: (view: {coverPhotoName && (
- +
)} @@ -9878,7 +9890,7 @@ function AccountProfileEditPage({ onNavigate, activeView }: { onNavigate: (view: {profileVisibility === 'publiczna' && 'Zobacz jak inni widzą Twój profil.'} {profileVisibility === 'kontakty' && 'Profil widzą tylko zaakceptowane kontakty.'} - @@ -10081,7 +10093,8 @@ function AccountProfileEditPage({ onNavigate, activeView }: { onNavigate: (view: ); } -function AccountPublicProfilePage({ onNavigate, activeView }: { onNavigate: (view: View) => void; activeView: View }) { +function AccountPublicProfilePage() { + const navigate = useNavigate(); const { user } = useAuth(); const publicProfileCover = window.localStorage.getItem(accountStorageKey('CoverImage', user)) || heroImage; const publicReviews = [ @@ -10154,7 +10167,7 @@ function AccountPublicProfilePage({ onNavigate, activeView }: { onNavigate: (vie return (
- +
Dane i ustawienia Jak inni widzą Twój profil
@@ -10163,7 +10176,7 @@ function AccountPublicProfilePage({ onNavigate, activeView }: { onNavigate: (vie

Jak inni widzą Twój profil

Zobacz, jak Twój profil prezentuje się publicznie dla innych użytkowników Mieszko.

- +
@@ -10855,7 +10868,8 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) { ); } -function HomePage({ onNavigate, filters, onOpenMap }: { onNavigate: (view: View) => void; filters: FilterState; onOpenMap: (city: string, mode?: 'buy' | 'rent') => void }) { +function HomePage({ filters, onOpenMap }: { filters: FilterState; onOpenMap: (city: string, mode?: 'buy' | 'rent') => void }) { + const navigate = useNavigate(); const [favoriteListings, setFavoriteListings] = useState>(new Set()); const toggleFavoriteListing = (location: string) => { @@ -10926,14 +10940,14 @@ function HomePage({ onNavigate, filters, onOpenMap }: { onNavigate: (view: View)
- - +
- onNavigate('buy')} /> + navigate(ROUTES.buy)} />
@@ -10950,7 +10964,7 @@ function HomePage({ onNavigate, filters, onOpenMap }: { onNavigate: (view: View)

{card.title}

{card.text}

- + ))}
@@ -11253,7 +11267,8 @@ function RentPage({ ); } -function AddListingPage({ onNavigate }: { onNavigate: (view: View) => void }) { +function AddListingPage() { + const navigate = useNavigate(); const [currentStep, setCurrentStep] = useState(0); const [listingDetails, setListingDetails] = useState(INITIAL_LISTING_DETAILS); const [location, setLocation] = useState(INITIAL_LISTING_LOCATION); @@ -12342,11 +12357,11 @@ function AddListingPage({ onNavigate }: { onNavigate: (view: View) => void }) { znajdziesz je w „Moich ogłoszeniach” ze statusem „Oczekuje”.

- -
@@ -12683,7 +12698,8 @@ async function downloadBuyingChecklistPdf() { } } -function GuideBuyingPage({ onNavigate }: { onNavigate: (view: View) => void }) { +function GuideBuyingPage() { + const navigate = useNavigate(); const scrollToId = (id: string) => { const target = document.getElementById(id); if (target) { @@ -12697,7 +12713,7 @@ function GuideBuyingPage({ onNavigate }: { onNavigate: (view: View) => void }) {
- @@ -13132,7 +13148,7 @@ function GuideBuyingPage({ onNavigate }: { onNavigate: (view: View) => void }) {

Gotowy, by znaleźć swoje mieszkanie?

Przeglądaj aktualne oferty dopasowane do Twoich potrzeb i budżetu.

- @@ -13143,19 +13159,19 @@ function GuideBuyingPage({ onNavigate }: { onNavigate: (view: View) => void }) {

Kalkulatory

- - - - @@ -13190,7 +13206,8 @@ function GuideBuyingPage({ onNavigate }: { onNavigate: (view: View) => void }) { ); } -function GuidesPage({ onNavigate }: { onNavigate: (view: View) => void }) { +function GuidesPage() { + const navigate = useNavigate(); return (
@@ -13235,13 +13252,13 @@ function GuidesPage({ onNavigate }: { onNavigate: (view: View) => void }) { key={topic.title} role={isBuying ? 'button' : undefined} tabIndex={isBuying ? 0 : undefined} - onClick={isBuying ? () => onNavigate('guideBuying') : undefined} + onClick={isBuying ? () => navigate(ROUTES.guideBuying) : undefined} onKeyDown={ isBuying ? (event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); - onNavigate('guideBuying'); + navigate(ROUTES.guideBuying); } } : undefined @@ -13358,7 +13375,8 @@ function GuidesPage({ onNavigate }: { onNavigate: (view: View) => void }) { ); } -function SellPage({ onNavigate }: { onNavigate: (view: View) => void }) { +function SellPage() { + const navigate = useNavigate(); return (
@@ -13385,7 +13403,7 @@ function SellPage({ onNavigate }: { onNavigate: (view: View) => void }) { Pełne wsparcie
- + Zajmie Ci to tylko 2 minuty
@@ -13429,7 +13447,7 @@ function SellPage({ onNavigate }: { onNavigate: (view: View) => void }) {

Sprawdź wartość swojego mieszkania

Bezpłatna wycena online w 30 sekund

Podaj adres mieszkania
- +
Przykładowa wycena Kraków, Zabłocie @@ -13512,7 +13530,7 @@ function SellPage({ onNavigate }: { onNavigate: (view: View) => void }) { Gotowy na szybką i bezpieczną sprzedaż? Dołącz do tysięcy osób, które już sprzedały z nami swoje mieszkanie.
- + Zajmie Ci to tylko 2 minuty
@@ -14552,7 +14570,8 @@ function formatPercent(value: number): string { return `${(value * 100).toLocaleString('pl-PL', { maximumFractionDigits: 1 })}%`; } -function CreditCalculatorPage({ onNavigate, onSearchWithBudget }: { onNavigate: (view: View) => void; onSearchWithBudget: (maxPrice: number) => void }) { +function CreditCalculatorPage({ onSearchWithBudget }: { onSearchWithBudget: (maxPrice: number) => void }) { + const navigate = useNavigate(); const { user } = useAuth(); const [form, setForm] = useState(INITIAL_CREDIT_FORM); const [highlight, setHighlight] = useState(false); @@ -14843,7 +14862,7 @@ function CreditCalculatorPage({ onNavigate, onSearchWithBudget }: { onNavigate: return (
- +
{pageBody}
@@ -14856,9 +14875,9 @@ function CreditCalculatorPage({ onNavigate, onSearchWithBudget }: { onNavigate:
- + - +
{pageBody}
@@ -14866,7 +14885,8 @@ function CreditCalculatorPage({ onNavigate, onSearchWithBudget }: { onNavigate: ); } -function ValuationPage({ onNavigate }: { onNavigate: (view: View) => void }) { +function ValuationPage() { + const navigate = useNavigate(); const [step, setStep] = useState(0); const [valuationForm, setValuationForm] = useState(INITIAL_VALUATION_FORM); const [valuationLocationSuggestOpen, setValuationLocationSuggestOpen] = useState(false); @@ -15023,9 +15043,9 @@ function ValuationPage({ onNavigate }: { onNavigate: (view: View) => void }) {
- + - +
@@ -15088,7 +15108,7 @@ function ValuationPage({ onNavigate }: { onNavigate: (view: View) => void }) {
- +
@@ -16662,7 +16682,8 @@ function historyShortMarketLabel(market: PriceHistoryMarket) { return tail; } -function PriceHistoryPage({ onNavigate, user }: { onNavigate: (view: View) => void; user: AuthUser | null }) { +function PriceHistoryPage({ user }: { user: AuthUser | null }) { + const navigate = useNavigate(); const [locationInput, setLocationInput] = useState(''); const [selectedMarket, setSelectedMarket] = useState(null); const [propertyType, setPropertyType] = useState('Mieszkanie'); @@ -16852,7 +16873,7 @@ function PriceHistoryPage({ onNavigate, user }: { onNavigate: (view: View) => vo const createCityAlert = () => { if (!user) { setCreateAlertFeedback('Zaloguj się, aby dodać alert cenowy.'); - onNavigate('login'); + navigate(ROUTES.login); return; } if (!selectedMarket) { @@ -16886,9 +16907,9 @@ function PriceHistoryPage({ onNavigate, user }: { onNavigate: (view: View) => vo
- + - +
@@ -18067,7 +18088,8 @@ function LocationPickerMap({ ); } -function MapSearchPage({ initialCity, offerType, onNavigate }: { initialCity: string; offerType: ApiOfferType; onNavigate: (view: View) => void }) { +function MapSearchPage({ initialCity, offerType }: { initialCity: string; offerType: ApiOfferType }) { + const navigate = useNavigate(); const knownInitialCity = findKnownCityKey(initialCity); const startCity = knownInitialCity ?? ''; const initialCityValue = initialCity.trim(); @@ -18316,7 +18338,7 @@ function MapSearchPage({ initialCity, offerType, onNavigate }: { initialCity: st return (
- @@ -18443,18 +18465,15 @@ function MapSearchPage({ initialCity, offerType, onNavigate }: { initialCity: st } function Header({ - activeView, - onNavigate, onLogout, hasUnreadFavorites, onOpenNotification, }: { - activeView: View; - onNavigate: (view: View) => void; onLogout: () => void; hasUnreadFavorites: boolean; onOpenNotification: (notification: DisplayNotification) => void; }) { + const navigate = useNavigate(); const { user } = useAuth(); const { notifications: serverNotifications, unreadCount } = useNotifications(); const isLoggedIn = Boolean(user); @@ -18524,15 +18543,15 @@ function Header({ }; }, [isAccountMenuOpen]); - const goToAccountSection = (targetView: View) => { + const goToAccountSection = (targetPath: string) => { setIsAccountMenuOpen(false); - onNavigate(targetView); + navigate(targetPath); }; return (
- + {isAdmin && ADMIN}
-