Ulubione na koncie - koniec kasowania zapisow przez pusta przegladarke
Deploy Polska Lokalnie / deploy (push) Successful in 12s
Deploy Polska Lokalnie / deploy (push) Successful in 12s
Dotad front po kazdej zmianie dyktowal serwerowi cala liste ulubionych.
Zalogowanie sie na innym urzadzeniu wysylalo "mam zero ulubionych"
i kasowalo zapisy, przez co wlasciciel ogloszenia tracil licznik zapisan.
Teraz: GET /api/listings/favorites zwraca ulubione konta, dodanie
i usuniecie ida pojedynczo (POST/DELETE /{id}/favorite), a wpisy z
przegladarki dopisuje jednorazowy /favorites/import. Endpoint
/favorites/sync usuniety razem z jego jedynym wywolaniem.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+51
-21
@@ -2782,33 +2782,40 @@ function App() {
|
||||
}, [user]);
|
||||
|
||||
useEffect(() => {
|
||||
setFavoriteListingsState(readUserJsonStorage<FavoriteListing[]>(user, FAVORITE_LISTINGS_STORAGE_SUFFIX, []));
|
||||
setFavoritesPreferredTab(readUserJsonStorage<FavoriteTab>(user, FAVORITES_PREFERRED_TAB_STORAGE_SUFFIX, 'buy'));
|
||||
setListingPriceAlertSubscriptionsState(readUserJsonStorage<ListingPriceAlertSubscription[]>(user, LISTING_PRICE_ALERTS_STORAGE_SUFFIX, []));
|
||||
}, [user]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
writeUserJsonStorage(user, FAVORITE_LISTINGS_STORAGE_SUFFIX, favoriteListingsState);
|
||||
}, [favoriteListingsState, user]);
|
||||
|
||||
// Ulubione zyja lokalnie (szybkie UI), ale wlasciciel ogloszenia musi widziec, ile osob je zapisalo.
|
||||
// Po kazdej zmianie wysylamy caly zbior - dziala niezaleznie od tego, gdzie uzytkownik kliknal serce.
|
||||
// Ulubione trzyma konto (/api/listings/favorites), wiec sa te same na kazdym urzadzeniu.
|
||||
// Wpisy zapisane wczesniej w przegladarce dopisujemy raz przy pierwszym wejsciu.
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
setFavoriteListingsState([]);
|
||||
return;
|
||||
}
|
||||
const listingIds = favoriteListingsState
|
||||
.map((favorite) => favorite.listingId)
|
||||
.filter((listingId): listingId is number => typeof listingId === 'number');
|
||||
let cancelled = false;
|
||||
const local = readUserJsonStorage<FavoriteListing[]>(user, FAVORITE_LISTINGS_STORAGE_SUFFIX, []);
|
||||
const localIds = local.map((item) => item.listingId).filter((id): id is number => typeof id === 'number');
|
||||
const load = localIds.length > 0
|
||||
? apiFetch<ApiListingSummary[]>('/listings/favorites/import', { method: 'POST', body: JSON.stringify({ listingIds: localIds }) })
|
||||
.then((items) => {
|
||||
writeUserJsonStorage<FavoriteListing[]>(user, FAVORITE_LISTINGS_STORAGE_SUFFIX, []);
|
||||
return items;
|
||||
})
|
||||
: apiFetch<ApiListingSummary[]>('/listings/favorites');
|
||||
load
|
||||
.then((items) => { if (!cancelled) { setFavoriteListingsState(items.map(toFavoriteListingFromSummary)); } })
|
||||
.catch(() => { if (!cancelled) { setFavoriteListingsState([]); } });
|
||||
return () => { cancelled = true; };
|
||||
}, [user]);
|
||||
|
||||
apiFetch<void>('/listings/favorites/sync', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ listingIds }),
|
||||
}).catch(() => {
|
||||
// Brak polaczenia - licznik zapisan dogoni stan przy nastepnej zmianie ulubionych.
|
||||
});
|
||||
}, [favoriteListingsState, user]);
|
||||
|
||||
// Zapis i usuniecie ulubionego ida na serwer pojedynczo - stan przegladarki nigdy nie
|
||||
// dyktuje serwerowi calej listy, wiec wejscie z nowego urzadzenia niczego nie kasuje.
|
||||
const saveFavoriteOnServer = (listingId: number, add: boolean) => {
|
||||
apiFetch<void>(`/listings/${listingId}/favorite`, { method: add ? 'POST' : 'DELETE' })
|
||||
.catch(() => { /* brak sieci - zmiana wroci przy nastepnym wejsciu na konto */ });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
writeUserJsonStorage(user, FAVORITES_PREFERRED_TAB_STORAGE_SUFFIX, favoritesPreferredTab);
|
||||
@@ -3241,6 +3248,7 @@ function App() {
|
||||
const toggleListingFavorite = (listing: ApiListingDetail) => {
|
||||
const listingKey = favoriteListingKey(listing.id);
|
||||
const alreadyFavorite = favoriteListingsState.some((item) => item.id === listingKey);
|
||||
saveFavoriteOnServer(listing.id, !alreadyFavorite);
|
||||
if (!alreadyFavorite) {
|
||||
notificationsCenter.notify({
|
||||
category: 'listing',
|
||||
@@ -3332,6 +3340,7 @@ function App() {
|
||||
const toggleSummaryFavorite = async (listing: ApiListingSummary) => {
|
||||
const listingKey = favoriteListingKey(listing.id);
|
||||
const exists = favoriteListingsState.some((item) => item.id === listingKey);
|
||||
saveFavoriteOnServer(listing.id, !exists);
|
||||
if (exists) {
|
||||
setUnreadFavoriteIds((prev) => {
|
||||
if (!prev.has(listingKey)) {
|
||||
@@ -6497,9 +6506,19 @@ function AccountSidebar() {
|
||||
.catch(() => { if (!cancelled) { setSavedSearchCount(0); } });
|
||||
return () => { cancelled = true; };
|
||||
}, [user, location.pathname]);
|
||||
const favoritesCount = user
|
||||
? readUserJsonStorage<FavoriteListing[]>(user, FAVORITE_LISTINGS_STORAGE_SUFFIX, []).length
|
||||
: 0;
|
||||
// Ulubione sa na koncie - licznik bierzemy z serwera, tak jak liczbe ofert i wyszukiwan.
|
||||
const [favoritesCount, setFavoritesCount] = useState(0);
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
setFavoritesCount(0);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
apiFetch<ApiListingSummary[]>('/listings/favorites')
|
||||
.then((items) => { if (!cancelled) { setFavoritesCount(items.length); } })
|
||||
.catch(() => { if (!cancelled) { setFavoritesCount(0); } });
|
||||
return () => { cancelled = true; };
|
||||
}, [user, location.pathname]);
|
||||
// Licznik obejmuje oba rodzaje alertow - lokalizacji i pojedynczych ogloszen -
|
||||
// dokladnie tak, jak lista na /konto/alerty-cenowe.
|
||||
const priceAlertsCount = user
|
||||
@@ -26297,7 +26316,17 @@ function FavoritesPage({
|
||||
return () => window.clearTimeout(timeoutId);
|
||||
}, [favoritesFeedback]);
|
||||
|
||||
// Usuniecie idzie takze na serwer - ulubione sa na koncie, nie w tej jednej przegladarce.
|
||||
const forgetOnServer = (listingId: number) => {
|
||||
apiFetch<void>(`/listings/${listingId}/favorite`, { method: 'DELETE' })
|
||||
.catch(() => { /* brak sieci - wpis wroci przy nastepnym wejsciu */ });
|
||||
};
|
||||
|
||||
const removeFavoriteListing = (id: string) => {
|
||||
const removed = favoritesState.find((listing) => listing.id === id);
|
||||
if (removed) {
|
||||
forgetOnServer(removed.listingId);
|
||||
}
|
||||
setFavoritesState((current) => current.filter((listing) => listing.id !== id));
|
||||
setMenuListingId((current) => (current === id ? null : current));
|
||||
setFavoritesFeedback('Usunięto ogłoszenie z ulubionych.');
|
||||
@@ -26363,6 +26392,7 @@ function FavoritesPage({
|
||||
return;
|
||||
}
|
||||
|
||||
favoritesState.forEach((listing) => forgetOnServer(listing.listingId));
|
||||
setFavoritesState([]);
|
||||
setMenuListingId(null);
|
||||
setFavoritesFeedback('Usunięto wszystkie ogłoszenia z ulubionych.');
|
||||
|
||||
@@ -566,3 +566,34 @@ test.describe('Spotkania', () => {
|
||||
await expect(page.locator('.messages-meeting-form')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
// Ulubione sa na koncie. Test pilnuje regresji: wejscie z pustej przegladarki NIE moze
|
||||
// skasowac ulubionych zapisanych wczesniej (kiedys front dyktowal serwerowi cala liste).
|
||||
test.describe('Ulubione', () => {
|
||||
test('przezywaja wyczyszczenie pamieci przegladarki', async ({ page }) => {
|
||||
await login(page);
|
||||
await goto(page, ROUTES.buy);
|
||||
await page.locator('.real-result-card').first().waitFor();
|
||||
const href = await page.locator('.real-result-card .card-stretched-link').first().getAttribute('href');
|
||||
await goto(page, href!);
|
||||
const title = await page.locator('.listing-detail-title, h1').first().innerText();
|
||||
|
||||
const heart = page.locator('button[aria-label*="ulubion"], .listing-detail-fav').first();
|
||||
await heart.click();
|
||||
await goto(page, ROUTES.favorites);
|
||||
await expect(page.locator('main')).toContainText(title.trim().slice(0, 20));
|
||||
|
||||
await page.evaluate(() => {
|
||||
const token = window.localStorage.getItem('polskalokalnie-auth-token');
|
||||
window.localStorage.clear();
|
||||
if (token) {
|
||||
window.localStorage.setItem('polskalokalnie-auth-token', token);
|
||||
}
|
||||
});
|
||||
await goto(page, ROUTES.favorites);
|
||||
await expect(page.locator('main')).toContainText(title.trim().slice(0, 20));
|
||||
|
||||
await page.locator('button[aria-label*="Usuń"], .favorites-card-remove').first().click();
|
||||
await expect(page.locator('main')).not.toContainText(title.trim().slice(0, 20));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user