górne menu - wiadomości
Deploy Polska Lokalnie / deploy (push) Successful in 22s

This commit is contained in:
2026-09-25 10:09:33 +02:00
parent 517e80d6bd
commit cf02f480ed
22 changed files with 577 additions and 154 deletions
+40 -53
View File
@@ -4876,7 +4876,6 @@ function AdminPage() {
<article><span>Imię i nazwisko</span><strong>{verificationUser.fullName || '-'}</strong></article>
<article><span>Adres e-mail</span><strong>{verificationUser.email}</strong></article>
<article><span>Numer telefonu</span><strong>{verificationUser.phone || 'Nie podano'}</strong></article>
<article><span>Data urodzenia</span><strong>{verificationUser.birthDate ? verificationUser.birthDate.split('-').reverse().join('.') : 'Nie podano'}</strong></article>
</>
)}
</div>
@@ -10102,7 +10101,6 @@ const OTP_RESEND_COOLDOWN_SECONDS = 60;
// Pojedynczy wiersz z przelacznikiem. Zapis idzie na serwer od razu po kliknieciu.
// Powiadomienia, ktorych serwis jeszcze nie wysyla - przelacznik jest nieaktywny i mowi wprost dlaczego.
const SETTINGS_NOT_SENT_YET = 'Wkrótce - tych wiadomości jeszcze nie wysyłamy.';
function SettingsToggleRow({ icon, label, hint, checked, disabled, onChange }: {
icon: string;
@@ -10164,7 +10162,6 @@ function AccountSettingsPage({ onSearchPreferencesSaved }: { onSearchPreferences
nick: '',
phonePrefix: DEFAULT_PHONE_COUNTRY.code as string,
phoneDigits: '',
birthDate: '',
address: '',
contactPreference: 'EMAIL_AND_PHONE' as ContactPreference,
preferredLanguage: 'PL' as PreferredLanguage,
@@ -10276,7 +10273,6 @@ function AccountSettingsPage({ onSearchPreferencesSaved }: { onSearchPreferences
const composedPhone = identity.phoneDigits ? `${identity.phonePrefix} ${identity.phoneDigits}` : '';
// OTP idzie na numer zapisany w bazie, wiec potwierdzac mozna dopiero zapisany numer.
const isPhoneSaved = composedPhone !== '' && composedPhone === (user?.phone ?? '');
const todayIsoDate = new Date().toISOString().slice(0, 10);
// Formularz wypelniamy danymi z serwera - tymi samymi, ktore uzytkownik podal przy rejestracji.
useEffect(() => {
@@ -10294,7 +10290,6 @@ function AccountSettingsPage({ onSearchPreferencesSaved }: { onSearchPreferences
nick: user.nick ?? '',
phonePrefix: phone.prefix,
phoneDigits: phone.digits,
birthDate: user.birthDate ?? '',
address: user.address ?? '',
contactPreference: user.contactPreference,
preferredLanguage: user.preferredLanguage,
@@ -10476,7 +10471,6 @@ function AccountSettingsPage({ onSearchPreferencesSaved }: { onSearchPreferences
fullName: user.fullName,
nick: user.nick ?? undefined,
phone: user.phone ?? undefined,
birthDate: user.birthDate ?? undefined,
address: user.address ?? undefined,
contactPreference: user.contactPreference,
preferredLanguage: next,
@@ -10522,9 +10516,8 @@ function AccountSettingsPage({ onSearchPreferencesSaved }: { onSearchPreferences
phone: composedPhone || undefined,
// NIP wysylamy tylko dla firmy (dla osoby prywatnej undefined = backend go nie zmienia).
nip: isCompany ? identity.nip.trim() : undefined,
// Firma nie ma daty urodzenia - nie wysylamy jej z konta firmowego.
birthDate: isCompany ? undefined : (identity.birthDate || undefined),
address: identity.address.trim() || undefined,
// Adres to siedziba firmy - konto prywatne go nie podaje i backend go nie przyjmie.
address: isCompany ? (identity.address.trim() || undefined) : undefined,
contactPreference: identity.contactPreference,
preferredLanguage: identity.preferredLanguage,
});
@@ -11026,29 +11019,20 @@ function AccountSettingsPage({ onSearchPreferencesSaved }: { onSearchPreferences
)}
</label>
{/* Data urodzenia dotyczy tylko osob prywatnych - firma jej nie ma. */}
{!isCompany && (
{/* Adres zbieramy tylko od firm - to siedziba przedsiebiorcy, dana jawna z CEIDG/KRS.
Osoba prywatna nie ma po co podawac adresu zamieszkania: do kontaktu sluzy e-mail
i telefon, a adres nieruchomosci stoi przy ogloszeniu. */}
{isCompany && (
<label>
<span>Data urodzenia (opcjonalnie)</span>
<span>Adres siedziby firmy (opcjonalnie)</span>
<input
type="date"
max={todayIsoDate}
value={identity.birthDate}
onChange={(event) => setIdentityField('birthDate', event.target.value)}
value={identity.address}
placeholder="Np. Warszawa, ul. Marszałkowska 1"
onChange={(event) => setIdentityField('address', event.target.value)}
/>
<small>Nie pokazujemy jej innym użytkownikom.</small>
</label>
)}
<label>
<span>{isCompany ? 'Adres siedziby firmy (opcjonalnie)' : 'Adres zamieszkania (opcjonalnie)'}</span>
<input
value={identity.address}
placeholder={isCompany ? 'Np. Warszawa, ul. Marszałkowska 1' : 'Np. Łódź, ul. Piotrkowska 12'}
onChange={(event) => setIdentityField('address', event.target.value)}
/>
</label>
<label>
<span>Preferencje kontaktu</span>
<select
@@ -11413,56 +11397,60 @@ function AccountSettingsPage({ onSearchPreferencesSaved }: { onSearchPreferences
{/* --- Powiadomienia --- */}
<section className="settings-card" aria-label="Powiadomienia">
<div className="settings-card-head">
<h2>Powiadomienia e-mail</h2>
<p>Wybierz, o czym chcesz otrzymywać wiadomości.</p>
<h2>Powiadomienia</h2>
<p>
Decydują, co trafia na <Link to={ROUTES.notifications}>listę powiadomień</Link> i pod dzwonek w nagłówku.
</p>
</div>
{/* Wlaczone jest tylko to, co serwis naprawde wysyla. Reszta czeka na zbudowanie
funkcji - lepiej powiedziec to wprost, niz pozwolic wlaczyc martwy przelacznik. */}
{/* Kazdy przelacznik ma po stronie serwera funkcje, ktora go sprawdza - nie ma tu
pozycji "na przyszlosc". Zgody marketingowe to osobna sprawa (art. 7 RODO),
wiec stoja nizej, pod wlasnym naglowkiem. */}
<div className="settings-list compact">
<SettingsToggleRow
icon="bell"
icon="search"
label="Nowe oferty z zapisanych wyszukiwań"
hint={SETTINGS_NOT_SENT_YET}
checked={false}
disabled
onChange={() => { /* funkcja jeszcze nie powstala */ }}
hint="Gdy pojawi się ogłoszenie pasujące do zapisanego wyszukiwania."
checked={settings?.notifySavedSearches ?? true}
disabled={isLoading}
onChange={(next) => { void patchSettings({ notifySavedSearches: next }); }}
/>
<SettingsToggleRow
icon="trend"
label="Alerty cenowe"
hint="Alerty pokazujemy na liście powiadomień - maili z nich nie wysyłamy."
checked={false}
disabled
onChange={() => { /* funkcja jeszcze nie powstala */ }}
hint="Zmiany cen z obserwowanych ogłoszeń i lokalizacji."
checked={settings?.notifyPriceAlerts ?? true}
disabled={isLoading}
onChange={(next) => { void patchSettings({ notifyPriceAlerts: next }); }}
/>
<SettingsToggleRow
icon="message"
label="Wiadomości od użytkowników"
label="Wiadomości i terminy oglądania"
hint="Wyłączenie nie ukrywa wiadomości - te czekają w zakładce Wiadomości."
checked={settings?.notifyMessages ?? true}
disabled={isLoading}
onChange={(next) => { void patchSettings({ notifyMessages: next }); }}
/>
<SettingsToggleRow
icon="gear"
label="Nowości i aktualizacje serwisu"
hint={SETTINGS_NOT_SENT_YET}
checked={false}
disabled
onChange={() => { /* funkcja jeszcze nie powstala */ }}
/>
</div>
<div className="settings-card-head settings-card-subhead">
<h3>Zgody marketingowe</h3>
<p>Dotyczą wiadomości wysyłanych przez nas. Możesz je wycofać w każdej chwili.</p>
</div>
<div className="settings-list compact">
<SettingsToggleRow
icon="mail"
label="Oferty promowane i newsletter"
hint="Zgoda marketingowa - decyduje o wysyłce kampanii."
hint="Wiadomości e-mail o nowościach i promocjach."
checked={consent.emailConsent}
disabled={isLoading}
onChange={(next) => { void updateConsent({ ...consent, emailConsent: next }); }}
/>
<SettingsToggleRow
icon="phone"
label="Powiadomienia SMS"
hint="Zgoda marketingowa na wiadomości SMS."
label="Wiadomości SMS"
hint="Ta sama treść, wysyłana SMS-em."
checked={consent.smsConsent}
disabled={isLoading}
onChange={(next) => { void updateConsent({ ...consent, smsConsent: next }); }}
@@ -23840,7 +23828,7 @@ function Header({
</div>
)}
{!isLoggedIn && (
<Link className="icon-text" to={ROUTES.login}>
<Link className="icon-text" to={ROUTES.login} title="Moje konto">
<Icon name="user" />
<span>Moje konto</span>
</Link>
@@ -25286,7 +25274,6 @@ function MessagesPage({
const updated = await updateProfile({
fullName: user.fullName,
phone,
birthDate: user.birthDate ?? undefined,
address: user.address ?? undefined,
contactPreference: user.contactPreference,
preferredLanguage: user.preferredLanguage,
+1 -3
View File
@@ -36,7 +36,6 @@ export type UserSettings = {
notifySavedSearches: boolean;
notifyPriceAlerts: boolean;
notifyMessages: boolean;
notifyProductNews: boolean;
// Lokalizacje sprawdzone w Historii cen (JSON) - historia przegladania, bez powiadomien.
priceHistoryMarkets: string | null;
searchLocations: string | null;
@@ -78,7 +77,6 @@ export type AuthUser = {
contactPreference: ContactPreference;
preferredLanguage: PreferredLanguage;
nip: string | null;
birthDate: string | null;
verified: boolean;
phoneVerified: boolean;
blocked: boolean;
@@ -132,7 +130,7 @@ export type ProfileUpdate = {
fullName: string;
nick?: string;
phone?: string;
birthDate?: string;
// Adres siedziby - tylko konta firmowe. Backend odrzuca go dla kont prywatnych.
address?: string;
// NIP firmy - pomijamy dla kont prywatnych. Pominiete (undefined) = backend nie zmienia NIP-u.
nip?: string;
-3
View File
@@ -10,7 +10,6 @@ const en: Record<string, string> = {
'Dane i ustawienia': 'Data and settings',
'Edytuj profil': 'Edit profile',
'Dodatkowe informacje': 'Additional information',
'Adres zamieszkania': 'Residential address',
'Preferencje kontaktu': 'Contact preferences',
'Język komunikacji': 'Communication language',
'Nie podano': 'Not provided',
@@ -97,7 +96,6 @@ const uk: Record<string, string> = {
'Dane i ustawienia': 'Дані та налаштування',
'Edytuj profil': 'Редагувати профіль',
'Dodatkowe informacje': 'Додаткова інформація',
'Adres zamieszkania': 'Адреса проживання',
'Preferencje kontaktu': 'Параметри контакту',
'Język komunikacji': 'Мова спілкування',
'Nie podano': 'Не вказано',
@@ -163,7 +161,6 @@ const de: Record<string, string> = {
'Dane i ustawienia': 'Daten und Einstellungen',
'Edytuj profil': 'Profil bearbeiten',
'Dodatkowe informacje': 'Zusätzliche Informationen',
'Adres zamieszkania': 'Wohnadresse',
'Preferencje kontaktu': 'Kontaktpräferenzen',
'Język komunikacji': 'Kommunikationssprache',
'Nie podano': 'Nicht angegeben',
+7 -6
View File
@@ -20,7 +20,7 @@ const PROVIDER = {
};
const SERVICE_NAME = 'Polska Lokalnie';
const LAST_UPDATED = '23 września 2026 r.';
const LAST_UPDATED = '25 września 2026 r.';
type LegalSection = {
id: string;
@@ -135,7 +135,7 @@ export function TermsPage() {
<ol>
<li><strong>Bezpłatne ogłoszenia</strong> - dodawanie, edycja i wstrzymywanie ofert sprzedaży oraz najmu, bez prowizji od transakcji.</li>
<li><strong>Wyszukiwanie i mapa</strong> - przeglądanie ofert z filtrami, przeglądanie ich na mapie oraz zawężanie wyników do widocznego fragmentu mapy.</li>
<li><strong>Konto i narzędzia osobiste</strong> - ulubione ogłoszenia, zapisane wyszukiwania, porównywarka ofert oraz panel z własnymi ogłoszeniami.</li>
<li><strong>Konto i narzędzia osobiste</strong> - ulubione ogłoszenia, zapisane wyszukiwania, porównywarka ofert oraz panel z własnymi ogłoszeniami. Gdy pojawi się Ogłoszenie pasujące do zapisanego wyszukiwania, informacja trafia na listę powiadomień w Platformie; Użytkownik może ją wyłączyć w ustawieniach konta.</li>
<li><strong>Alerty cenowe</strong> - obserwowanie ceny wybranego Ogłoszenia lub średniej ceny za m² w mieście. Zmiany Platforma sprawdza raz na dobę, a informacja o nich trafia <strong>wyłącznie na listę powiadomień w Platformie</strong>. Z alertów nie są wysyłane wiadomości e-mail ani SMS.</li>
<li><strong>Wiadomości</strong> - kontakt między Użytkownikami w sprawie konkretnego Ogłoszenia, z możliwością zablokowania rozmówcy oraz kontakt z obsługą serwisu.</li>
<li><strong>Umawianie oglądania</strong> - proponowanie terminu w rozmowie o Ogłoszeniu; druga strona potwierdza go lub odrzuca, a każda ze stron może termin odwołać. Terminy są widoczne w kalendarzu obu stron.</li>
@@ -168,10 +168,10 @@ export function TermsPage() {
body: (
<ol>
<li>Rejestracja wymaga podania adresu e-mail, hasła (co najmniej 8 znaków) oraz imienia i nazwiska albo nazwy firmy, a także akceptacji Regulaminu i Polityki prywatności. Moment akceptacji jest zapisywany.</li>
<li>Konto zakłada osoba pełnoletnia posiadająca pełną zdolność do czynności prawnych, we własnym imieniu albo z umocowaniem do działania za inny podmiot.</li>
<li>Konto zakłada osoba pełnoletnia posiadająca pełną zdolność do czynności prawnych, we własnym imieniu albo z umocowaniem do działania za inny podmiot. Pełnoletność potwierdza oświadczenie Użytkownika złożone przez założenie Konta - Platforma nie zbiera w tym celu daty urodzenia ani nie żąda dokumentu tożsamości.</li>
<li>Konto wymaga aktywacji linkiem wysłanym na podany adres e-mail. Do tego czasu logowanie jest zablokowane.</li>
<li>Numer telefonu jest dobrowolny. Użytkownik może go potwierdzić kodem SMS; niepotwierdzony numer nie blokuje korzystania z Platformy.</li>
<li>Przy rejestracji konta firmowego dane firmy mogą zostać pobrane z rejestru REGON prowadzonego przez Główny Urząd Statystyczny na podstawie podanego numeru NIP.</li>
<li>Przy rejestracji konta firmowego dane firmy mogą zostać pobrane z rejestru REGON prowadzonego przez Główny Urząd Statystyczny na podstawie podanego numeru NIP. Adres siedziby jest polem konta firmowego; Użytkownik będący osobą prywatną nie podaje adresu zamieszkania.</li>
<li>Logowanie odbywa się wyłącznie adresem e-mail i hasłem. Platforma nie oferuje logowania za pośrednictwem kont w serwisach zewnętrznych.</li>
<li>Konto jest przypisane do jednego Użytkownika. Udostępnianie danych logowania osobom trzecim jest niedozwolone.</li>
<li>Użytkownik może w każdej chwili usunąć Konto w panelu konta. Skutki usunięcia opisuje <Link to={ROUTES.privacy}>Polityka prywatności</Link>.</li>
@@ -416,7 +416,7 @@ export function PrivacyPolicyPage() {
title: '2. Jakie dane przetwarzamy',
body: (
<ul>
<li><strong>Dane konta</strong> - adres e-mail, hasło w postaci zaszyfrowanej (funkcja skrótu), imię i nazwisko lub nazwa firmy, nazwa użytkownika, opcjonalnie numer telefonu, adres i NIP.</li>
<li><strong>Dane konta</strong> - adres e-mail, hasło w postaci zaszyfrowanej (funkcja skrótu), imię i nazwisko lub nazwa firmy, nazwa użytkownika oraz opcjonalnie numer telefonu. Przy koncie firmowym dodatkowo NIP i adres siedziby. Od osób prywatnych nie zbieramy adresu zamieszkania ani daty urodzenia.</li>
<li><strong>Dane profilu</strong> - opis, zdjęcie profilowe i tło, adres strony i profili społecznościowych, preferencje wyszukiwania - jeśli Użytkownik je poda.</li>
<li><strong>Treści</strong> - ogłoszenia wraz ze zdjęciami i lokalizacją, artykuły w poradnikach, wiadomości do innych Użytkowników i do obsługi serwisu.</li>
<li><strong>Aktywność na koncie</strong> - ulubione ogłoszenia, zapisane wyszukiwania, alerty cenowe, umówione terminy oglądania, powiadomienia.</li>
@@ -449,7 +449,8 @@ export function PrivacyPolicyPage() {
<ul>
<li>Adres e-mail, hasło oraz imię i nazwisko lub nazwa są niezbędne do założenia konta - bez nich nie możemy świadczyć usługi.</li>
<li>NIP jest niezbędny przy koncie firmowym.</li>
<li>Numer telefonu, adres, opis profilu i zdjęcia są dobrowolne. Ich brak ogranicza jedynie wybrane funkcje (np. kontakt telefoniczny z ogłoszenia).</li>
<li>Numer telefonu, adres siedziby firmy, opis profilu i zdjęcia są dobrowolne. Ich brak ogranicza jedynie wybrane funkcje (np. kontakt telefoniczny z ogłoszenia).</li>
<li>Zbieramy wyłącznie dane, które są potrzebne do działania konkretnej funkcji serwisu. Pól „na wszelki wypadek" nie ma - jeżeli dane nie mają zastosowania, usuwamy je z formularzy i z bazy.</li>
<li>Przeglądanie ogłoszeń nie wymaga zakładania konta.</li>
</ul>
),
+32 -26
View File
@@ -129,13 +129,7 @@ svg {
font-size: 18px;
}
.header-admin .icon-text span {
color: #dbe4f0;
font-size: 12px;
}
.header-admin .icon-text.active svg,
.header-admin .icon-text.active span {
.header-admin .icon-text.active svg {
color: #34d399;
}
@@ -276,18 +270,23 @@ svg {
font-size: 20px;
}
.icon-text span {
font-size: 13px;
font-weight: 800;
/* Skroty w pasku gornym to same ikony - jeden jezyk dla ulubionych, wiadomosci, dzwonka
i konta. Podpis zostaje w znaczniku (czytniki ekranu, dymek title), ale go nie rysujemy.
Awatar konta to tez <span>, wiec musi byc wyjety spod tej reguly. */
.icon-text span:not(.avatar) {
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
.icon-text.active {
color: #109457;
}
.icon-text.active svg,
.icon-text.active span {
.icon-text.active svg {
color: #109457;
}
@@ -295,13 +294,7 @@ svg {
position: relative;
}
/* Ponizej 1560 px menu glowne nie miescilo sie miedzy logo a panelem i nachodzilo na oba -
"Ulubione" zostaje samym sercem (jak dzwonek i wiadomosci), podpis jest w dymku. */
@media (max-width: 1560px) {
.favorites-shortcut span {
display: none;
}
.header {
gap: 20px;
}
@@ -12411,8 +12404,7 @@ a.city-card:hover {
gap: 17px;
}
.nav button, .nav a,
.icon-text span {
.nav button, .nav a {
font-size: 12px;
}
@@ -12673,12 +12665,6 @@ a.city-card:hover {
flex-wrap: nowrap;
}
/* Podpisy skrotow ustepuja miejsca, ale awatar konta to tez <span> - musi zostac,
inaczej po zalogowaniu w naglowku wisi sama strzalka menu. */
.icon-text span:not(.avatar) {
display: none;
}
.notification-popover {
right: -18px;
top: calc(100% + 12px);
@@ -20728,6 +20714,26 @@ a.city-card:hover {
justify-content: space-between;
}
/* Drugi blok w tej samej karcie (zgody marketingowe pod powiadomieniami serwisu) -
oddzielony kreska, z mniejszym naglowkiem niz tytul karty. */
.settings-card-subhead {
border-top: 1px solid #e8edf3;
margin-top: 18px;
padding-top: 16px;
}
.settings-card-subhead h3 {
color: #1a2c46;
font-size: 14px;
font-weight: 900;
margin: 0;
}
.settings-card-head a {
color: #0f8f55;
font-weight: 850;
}
.settings-card-head.split > button,
.settings-profile-row > button, .settings-profile-row > a {
background: #ffffff;
+80
View File
@@ -773,3 +773,83 @@ test.describe('Historia cen', () => {
await expect(page.locator('.history-chart-empty')).toContainText('Wybierz lokalizację');
});
});
// Minimalizacja danych (art. 5 ust. 1 lit. c RODO): w ustawieniach konta zbieramy tylko to,
// czego uzywa jakas funkcja serwisu. Data urodzenia zniknela calkowicie, adres zostal firmom.
test.describe('Zakres danych w ustawieniach konta', () => {
test('konto prywatne nie ma daty urodzenia ani adresu zamieszkania', async ({ page }) => {
await login(page);
await goto(page, ROUTES.accountSettings);
await expect(page.getByText('Data urodzenia')).toHaveCount(0);
await expect(page.getByText('Adres zamieszkania')).toHaveCount(0);
await expect(page.getByText('Adres siedziby')).toHaveCount(0);
// Pola, ktore maja zastosowanie, zostaja na miejscu.
await expect(page.getByText('Numer telefonu').first()).toBeVisible();
});
test('backend nie zapisze adresu przyslanego z konta prywatnego', async ({ page }) => {
await login(page);
const token = await page.evaluate(() => window.localStorage.getItem('polskalokalnie-auth-token'));
expect(token).toBeTruthy();
const me = await page.request.get('/api/auth/me', { headers: { Authorization: `Bearer ${token}` } }).then((r) => r.json());
expect(me.accountType).toBe('PERSONAL');
const updated = await page.request.put('/api/auth/me', {
headers: { Authorization: `Bearer ${token}` },
data: {
fullName: me.fullName,
phone: me.phone ?? undefined,
address: 'ul. Podrzucona 1, Warszawa',
contactPreference: me.contactPreference,
preferredLanguage: me.preferredLanguage,
},
}).then((r) => r.json());
expect(updated.address).toBeNull();
// Data urodzenia nie jest juz czescia profilu w API.
expect(updated.birthDate).toBeUndefined();
});
});
// Przelaczniki powiadomien maja realnie sterowac lista na /powiadomienia - nie byc ozdoba.
test.describe('Ustawienia powiadomien', () => {
test('w karcie nie ma martwych przelacznikow', async ({ page }) => {
await login(page);
await goto(page, ROUTES.accountSettings);
const card = page.locator('section[aria-label="Powiadomienia"]');
await expect(card).toBeVisible();
await expect(card).not.toContainText('Wkrótce');
// Kazdy przelacznik w karcie da sie klikac.
const switches = card.getByRole('switch');
await expect(switches).toHaveCount(5);
for (let index = 0; index < 5; index += 1) {
await expect(switches.nth(index)).toBeEnabled();
}
// Naglowek prowadzi na liste powiadomien.
await card.getByRole('link', { name: 'listę powiadomień' }).click();
await expect(page).toHaveURL(new RegExp(`${ROUTES.notifications}$`));
});
test('wylaczenie zapisanych wyszukiwan przezywa odswiezenie', async ({ page }) => {
await login(page);
await goto(page, ROUTES.accountSettings);
const row = page.locator('.toggle-row').filter({ hasText: 'Nowe oferty z zapisanych wyszukiwań' });
const toggle = row.getByRole('switch');
const wasOn = await toggle.getAttribute('aria-checked');
await toggle.click();
await page.waitForTimeout(700);
await page.reload();
await page.waitForTimeout(1200);
await expect(page.locator('.toggle-row').filter({ hasText: 'Nowe oferty z zapisanych wyszukiwań' }).getByRole('switch'))
.toHaveAttribute('aria-checked', wasOn === 'true' ? 'false' : 'true');
// Sprzatanie: wracamy do stanu sprzed testu.
await page.locator('.toggle-row').filter({ hasText: 'Nowe oferty z zapisanych wyszukiwań' }).getByRole('switch').click();
await page.waitForTimeout(700);
});
});