Dodaj skrot do wiadomosci w pasku gornym
Deploy Polska Lokalnie / deploy (push) Successful in 10s

Obok "Ulubione" i dzwonka powiadomien - dla kazdego zalogowanego konta,
takze administratora - ikona prowadzaca do /wiadomosci z plakietka liczby
nieprzeczytanych, na wzor Gieldy Komunalnej.

Licznik nieprzeczytanych wydzielony do useMessagesUnreadCount, wspolnego
dla menu konta i paska gornego; odswieza sie co 25 s i po zmianie strony.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-21 23:26:08 +02:00
parent 5eee33b986
commit 9615f131f4
2 changed files with 75 additions and 32 deletions
+54 -32
View File
@@ -6354,13 +6354,54 @@ function NegotiationPage({
); );
} }
// Nieprzeczytane wiadomosci (rozmowy o ogloszeniach + rozmowa z administracja) dla kazdego
// zalogowanego konta - admin tez moze sprzedawac. Wspolne dla menu konta i paska gornego;
// odswiezane co 25 s oraz po zmianie strony, np. po otwarciu rozmowy.
function useMessagesUnreadCount(): number {
const { user } = useAuth();
const { pathname } = useLocation();
const [count, setCount] = useState(0);
useEffect(() => {
if (!user) {
setCount(0);
return;
}
let cancelled = false;
const loadUnread = () => {
apiFetch<{ count: number }>('/messages/unread-count')
.then((data) => {
if (!cancelled) {
setCount(data.count);
}
})
.catch(() => {
if (!cancelled) {
setCount(0);
}
});
};
loadUnread();
const intervalId = window.setInterval(loadUnread, 25000);
return () => {
cancelled = true;
window.clearInterval(intervalId);
};
}, [user, pathname]);
return count;
}
function AccountSidebar() { function AccountSidebar() {
const { user } = useAuth(); const { user } = useAuth();
const location = useLocation(); const location = useLocation();
// Ustawienia obejmuja takze podglad profilu publicznego - podswietlamy je wspolna pozycja menu. // Ustawienia obejmuja takze podglad profilu publicznego - podswietlamy je wspolna pozycja menu.
const isSettingsSection = location.pathname === ROUTES.accountSettings const isSettingsSection = location.pathname === ROUTES.accountSettings
|| location.pathname === ROUTES.accountPublicProfile; || location.pathname === ROUTES.accountPublicProfile;
const [messagesUnreadCount, setMessagesUnreadCount] = useState(0); const messagesUnreadCount = useMessagesUnreadCount();
// Liczba ogloszen konta pochodzi z serwera (/listings/mine) - tak samo jak lista na /konto/moje-oferty. // Liczba ogloszen konta pochodzi z serwera (/listings/mine) - tak samo jak lista na /konto/moje-oferty.
const [allListingsCount, setAllListingsCount] = useState(0); const [allListingsCount, setAllListingsCount] = useState(0);
// Spotkania zaplanowane na dzisiaj (odwolane sie nie licza). // Spotkania zaplanowane na dzisiaj (odwolane sie nie licza).
@@ -6394,37 +6435,6 @@ function AccountSidebar() {
+ readUserJsonStorage<ListingPriceAlertSubscription[]>(user, LISTING_PRICE_ALERTS_STORAGE_SUFFIX, []).length + readUserJsonStorage<ListingPriceAlertSubscription[]>(user, LISTING_PRICE_ALERTS_STORAGE_SUFFIX, []).length
: 0; : 0;
// Admin tez moze sprzedawac, wiec licznik rozmow o ogloszeniach dotyczy kazdego konta.
useEffect(() => {
if (!user) {
setMessagesUnreadCount(0);
return;
}
let cancelled = false;
const loadUnread = () => {
apiFetch<{ count: number }>('/messages/unread-count')
.then((data) => {
if (!cancelled) {
setMessagesUnreadCount(data.count);
}
})
.catch(() => {
if (!cancelled) {
setMessagesUnreadCount(0);
}
});
};
loadUnread();
const intervalId = window.setInterval(loadUnread, 25000);
return () => {
cancelled = true;
window.clearInterval(intervalId);
};
}, [user]);
useEffect(() => { useEffect(() => {
if (!user) { if (!user) {
setAllListingsCount(0); setAllListingsCount(0);
@@ -23368,6 +23378,7 @@ function Header({
const { notifications: serverNotifications, unreadCount } = useNotifications(); const { notifications: serverNotifications, unreadCount } = useNotifications();
const isLoggedIn = Boolean(user); const isLoggedIn = Boolean(user);
const isAdmin = user?.role === 'ADMIN'; const isAdmin = user?.role === 'ADMIN';
const messagesUnreadCount = useMessagesUnreadCount();
const [isNotificationsOpen, setIsNotificationsOpen] = useState(false); const [isNotificationsOpen, setIsNotificationsOpen] = useState(false);
const notificationPopoverRef = useRef<HTMLDivElement | null>(null); const notificationPopoverRef = useRef<HTMLDivElement | null>(null);
const [isAccountMenuOpen, setIsAccountMenuOpen] = useState(false); const [isAccountMenuOpen, setIsAccountMenuOpen] = useState(false);
@@ -23482,6 +23493,17 @@ function Header({
<span>Ulubione</span> <span>Ulubione</span>
{hasUnreadFavorites && <i aria-hidden="true" />} {hasUnreadFavorites && <i aria-hidden="true" />}
</NavLink> </NavLink>
{isLoggedIn && (
<NavLink
to={ROUTES.messages}
className={({ isActive }) => (isActive ? 'icon-text messages-shortcut active' : 'icon-text messages-shortcut')}
aria-label={messagesUnreadCount > 0 ? `Wiadomości, nieprzeczytane: ${messagesUnreadCount}` : 'Wiadomości'}
title="Wiadomości"
>
<Icon name="message" />
{messagesUnreadCount > 0 && <b aria-hidden="true">{messagesUnreadCount > 99 ? '99+' : messagesUnreadCount}</b>}
</NavLink>
)}
{isLoggedIn && ( {isLoggedIn && (
<div className="notify-bell-wrap" ref={notificationPopoverRef}> <div className="notify-bell-wrap" ref={notificationPopoverRef}>
<button <button
+21
View File
@@ -288,6 +288,27 @@ svg {
position: relative; position: relative;
} }
/* Skrot do wiadomosci w pasku gornym - plakietka z liczba nieprzeczytanych. */
.messages-shortcut {
position: relative;
}
.messages-shortcut b {
background: #14a45f;
border: 2px solid #ffffff;
border-radius: 999px;
color: #ffffff;
font-size: 10px;
font-weight: 900;
line-height: 14px;
min-width: 18px;
padding: 0 4px;
position: absolute;
right: -10px;
text-align: center;
top: -8px;
}
.favorites-shortcut i { .favorites-shortcut i {
background: #14a45f; background: #14a45f;
border: 2px solid #ffffff; border: 2px solid #ffffff;