ladowanie ogloszen
Deploy Polska Lokalnie / deploy (push) Successful in 11s

This commit is contained in:
2026-09-05 21:10:33 +02:00
parent c981b3cb93
commit fbfb0db38e
2 changed files with 179 additions and 1 deletions
+65 -1
View File
@@ -14240,6 +14240,7 @@ function AddListingPage({ editListingId }: { editListingId?: number }) {
// dalej, zeby nie krzyczec o brakach zanim uzytkownik cokolwiek wypelni.
const [showDetailsError, setShowDetailsError] = useState(false);
const [showLocationError, setShowLocationError] = useState(false);
const [showPhotosError, setShowPhotosError] = useState(false);
const [listingDetails, setListingDetails] = useState(INITIAL_LISTING_DETAILS);
// Ekran startowy z wyborem "Chcę sprzedać / wynająć" + kategorii. Przy edycji pomijamy go i od razu
// pokazujemy formularz. Wybor kategorii ustawia transakcje i rodzaj, a potem wchodzimy w kreator.
@@ -14640,8 +14641,17 @@ function AddListingPage({ editListingId }: { editListingId?: number }) {
});
return;
}
// Krok "Zdjęcia" (currentStep 2) - wymagamy przynajmniej jednego zdjecia, zeby przejsc dalej.
if (currentStep === 2 && step > 2 && photos.length === 0) {
setShowPhotosError(true);
window.requestAnimationFrame(() => {
document.querySelector('.add-step-error')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
});
return;
}
setShowDetailsError(false);
setShowLocationError(false);
setShowPhotosError(false);
setCurrentStep(step);
window.requestAnimationFrame(() => {
document.querySelector('.add-page')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
@@ -15328,6 +15338,12 @@ function AddListingPage({ editListingId }: { editListingId?: number }) {
{virtualTourError && <p className="photo-tour-error" role="alert">{virtualTourError}</p>}
</div>
{showPhotosError && photos.length === 0 && (
<p className="add-field-error add-step-error" role="alert">
Dodaj przynajmniej jedno zdjęcie, aby przejść dalej.
</p>
)}
<div className="add-location-actions photos-actions">
<button className="back-step-button" type="button" onClick={() => goToStep(1)}>
Wstecz
@@ -22518,6 +22534,45 @@ function ResultCardGallery({ photos, cover, alt }: { photos: string[]; cover: st
);
}
// Animacja wczytywania listy ofert - pokazywana zamiast pustego miejsca, dopoki backend nie odda
// ogloszen. Znika sama, gdy dane przyjda (komponent nadrzedny przestaje ja renderowac).
function ListingsLoader() {
return (
<div className="listings-loader" role="status" aria-live="polite">
<div className="listings-loader-art" aria-hidden="true">
<svg viewBox="0 0 240 150" width="200" height="125" role="img">
<path d="M42 118a78 78 0 0 1 156 0Z" fill="#e8f5ed" />
<g className="ll-cloud ll-cloud-a" fill="#eef2f6">
<ellipse cx="58" cy="44" rx="17" ry="9" />
<ellipse cx="70" cy="40" rx="12" ry="8" />
</g>
<g className="ll-cloud ll-cloud-b" fill="#eef2f6">
<ellipse cx="188" cy="52" rx="15" ry="8" />
<ellipse cx="177" cy="48" rx="10" ry="7" />
</g>
<g>
<rect x="58" y="96" width="7" height="22" rx="3" fill="#c3ddca" />
<circle cx="61.5" cy="90" r="16" fill="#d4ead9" />
</g>
<g className="ll-house">
<rect x="96" y="86" width="60" height="34" rx="3" fill="#ffffff" stroke="#cadfd2" strokeWidth="2.5" />
<path d="M126 58l34 30H92Z" fill="#d4ead9" stroke="#9ecfae" strokeWidth="2.5" strokeLinejoin="round" />
<rect x="150" y="66" width="8" height="16" rx="2" fill="#c3ddca" />
<rect x="118" y="98" width="16" height="22" rx="2" fill="#38b26a" />
<circle cx="130" cy="109" r="1.6" fill="#ffffff" />
</g>
<rect x="42" y="117" width="156" height="3" rx="1.5" fill="#d9ebe0" />
</svg>
</div>
<p className="listings-loader-title">Wczytujemy <span>ogłoszenia</span>…</p>
<p className="listings-loader-sub">Zaraz pokażemy Ci najlepsze oferty dopasowane do Twoich kryteriów.</p>
<div className="listings-loader-bars" aria-hidden="true">
<span /><span /><span /><span />
</div>
</div>
);
}
function RealListingsBand({
offerType,
onOpenListing,
@@ -22553,7 +22608,16 @@ function RealListingsBand({
const cityFilter = (city ?? '').trim();
const visible = cityFilter ? items.filter((item) => cityStartsWith(item.city, cityFilter)) : items;
if (loading || visible.length === 0) {
// W trakcie ladowania pokazujemy animacje zamiast pustego miejsca; gdy dane przyjda i lista jest
// pusta - nie pokazujemy nic (jak dotad).
if (loading) {
return (
<div className="real-listings-section" aria-label="Ogłoszenia dodane w serwisie">
<ListingsLoader />
</div>
);
}
if (visible.length === 0) {
return null;
}
+114
View File
@@ -26568,6 +26568,107 @@ a.city-card:hover {
margin-bottom: 18px;
}
/* Animacja wczytywania ofert - wypelnia miejsce listy, dopoki backend nie odda ogloszen. */
.listings-loader {
align-items: center;
background: linear-gradient(180deg, #f6faf7 0%, #ffffff 100%);
border: 1px solid #e7eef0;
border-radius: 16px;
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 18px;
padding: 30px 24px 34px;
text-align: center;
}
.listings-loader-art {
animation: ll-float 2.6s ease-in-out infinite;
}
.ll-house {
animation: ll-pop 2.6s ease-in-out infinite;
transform-origin: 126px 118px;
}
.ll-cloud-a { animation: ll-drift 5s ease-in-out infinite; }
.ll-cloud-b { animation: ll-drift 6.5s ease-in-out infinite reverse; }
.listings-loader-title {
color: #13243d;
font-size: 22px;
font-weight: 900;
margin: 6px 0 0;
}
.listings-loader-title span { color: #10a15e; }
.listings-loader-sub {
color: #7b8896;
font-size: 14px;
margin: 0;
}
.listings-loader-bars {
display: flex;
gap: 10px;
margin-top: 14px;
}
.listings-loader-bars span {
background: #e4ebe6;
border-radius: 999px;
height: 6px;
overflow: hidden;
width: 46px;
}
.listings-loader-bars span::after {
animation: ll-fill 1.4s ease-in-out infinite;
background: #10a15e;
border-radius: inherit;
content: '';
display: block;
height: 100%;
transform: translateX(-100%);
width: 100%;
}
.listings-loader-bars span:nth-child(2)::after { animation-delay: 0.18s; }
.listings-loader-bars span:nth-child(3)::after { animation-delay: 0.36s; }
.listings-loader-bars span:nth-child(4)::after { animation-delay: 0.54s; }
@keyframes ll-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-6px); }
}
@keyframes ll-pop {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.04); }
}
@keyframes ll-drift {
0%, 100% { transform: translateX(0); }
50% { transform: translateX(6px); }
}
@keyframes ll-fill {
0% { transform: translateX(-100%); }
55%, 100% { transform: translateX(100%); }
}
@media (prefers-reduced-motion: reduce) {
.listings-loader-art,
.ll-house,
.ll-cloud-a,
.ll-cloud-b,
.listings-loader-bars span::after {
animation: none;
}
.listings-loader-bars span::after { transform: translateX(0); }
}
.real-listings-heading {
align-items: center;
display: flex;
@@ -30956,6 +31057,19 @@ a.listing-detail-back {
}
/* Widok KOMPAKTOWY: gestsze wiersze, mniejsze zdjecie, bez metadanych. */
/* Widok LISTA (domyslny): karty nieco wieksze niz bazowe - szersze zdjecie i wyzszy kafelek.
Tylko od 761px w gore, zeby nie ruszac ukladu mobilnego (ponizej 760px karty i tak sie skladaja). */
@media (min-width: 761px) {
.result-list.view-lista .result-card {
grid-template-columns: 320px minmax(0, 1fr) 190px;
min-height: 232px;
}
.result-list.view-lista .result-photo {
min-height: 232px;
}
}
.result-list.view-kompaktowa { gap: 8px; }
.result-list.view-kompaktowa .result-card {