+97
-9
@@ -1833,6 +1833,9 @@ const sellTestimonials = [
|
||||
|
||||
const addProgressSteps = ['Szczegóły', 'Lokalizacja', 'Zdjęcia', 'Dodatkowe informacje', 'Podgląd ogłoszenia'];
|
||||
|
||||
// Minimalna dlugosc opisu ogloszenia w znakach - warunek przejscia z kroku "Szczegóły" dalej.
|
||||
const MIN_DESCRIPTION_CHARS = 30;
|
||||
|
||||
const listingTips = [
|
||||
{
|
||||
icon: 'document',
|
||||
@@ -3244,7 +3247,11 @@ function locationZoom(location: ListingLocation) {
|
||||
return 12;
|
||||
}
|
||||
|
||||
async function geocodeListingLocation(location: ListingLocation, signal: AbortSignal): Promise<LatLng | null> {
|
||||
// Wynik geokodowania adresu: pozycja pinezki plus informacja, czy trafilismy w konkretny budynek
|
||||
// (Nominatim zwrocil numer domu). Ta precyzja decyduje, czy da sie pominac reczne pola ulicy i numeru.
|
||||
type GeocodeResult = { point: LatLng; precise: boolean };
|
||||
|
||||
async function geocodeListingLocation(location: ListingLocation, signal: AbortSignal): Promise<GeocodeResult | null> {
|
||||
const query = locationSearchQuery(location);
|
||||
if (!location.city.trim() || query.length < 4) {
|
||||
return null;
|
||||
@@ -3264,7 +3271,10 @@ async function geocodeListingLocation(location: ListingLocation, signal: AbortSi
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
return { lat: Number(result.lat), lng: Number(result.lon) };
|
||||
return {
|
||||
point: { lat: Number(result.lat), lng: Number(result.lon) },
|
||||
precise: Boolean(result.address?.house_number),
|
||||
};
|
||||
}
|
||||
|
||||
async function reverseGeocodeListingLocation(point: LatLng, signal: AbortSignal): Promise<Partial<ListingLocation>> {
|
||||
@@ -14226,6 +14236,10 @@ function AddListingPage({ editListingId }: { editListingId?: number }) {
|
||||
const isEdit = typeof editListingId === 'number';
|
||||
const [editLoadError, setEditLoadError] = useState('');
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
// Komunikat blokady kroku "Szczegóły"/"Lokalizacja" - pokazywany dopiero po próbie przejścia
|
||||
// dalej, zeby nie krzyczec o brakach zanim uzytkownik cokolwiek wypelni.
|
||||
const [showDetailsError, setShowDetailsError] = useState(false);
|
||||
const [showLocationError, setShowLocationError] = 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.
|
||||
@@ -14283,6 +14297,9 @@ function AddListingPage({ editListingId }: { editListingId?: number }) {
|
||||
const [locationMapCenter, setLocationMapCenter] = useState<LatLng>(INITIAL_LISTING_COORDS);
|
||||
const [locationMapZoom, setLocationMapZoom] = useState(16);
|
||||
const [locationStatus, setLocationStatus] = useState('Kliknij na mapie albo wpisz adres, a pinezka ustawi się automatycznie.');
|
||||
// true = mapa odnalazla konkretny budynek (adres z numerem) - wtedy nie zmuszamy do recznego
|
||||
// wpisania ulicy i numeru, bo pinezka juz jednoznacznie wskazuje lokalizacje.
|
||||
const [locationResolved, setLocationResolved] = useState(false);
|
||||
const skipLocationSearch = useRef(false);
|
||||
const photoInputRef = useRef<HTMLInputElement>(null);
|
||||
const photoUrlsRef = useRef<string[]>([]);
|
||||
@@ -14305,6 +14322,8 @@ function AddListingPage({ editListingId }: { editListingId?: number }) {
|
||||
const formattedDeposit = numericDeposit > 0 ? `Czynsz administracyjny: ${new Intl.NumberFormat('pl-PL').format(numericDeposit)} zł` : 'Czynsz administracyjny: nie podano';
|
||||
const formattedSecurityDeposit = numericSecurityDeposit > 0 ? `${new Intl.NumberFormat('pl-PL').format(numericSecurityDeposit)} zł` : 'Nie podano';
|
||||
const previewTitle = listingDetails.title.trim() || 'Tytuł ogłoszenia';
|
||||
// Dlugosc opisu w znakach liczona z czystego tekstu (bez znacznikow HTML edytora) - do minimum 30.
|
||||
const descriptionCharCount = stripHtml(listingDetails.description).trim().length;
|
||||
const normalizedVirtualTourUrl = extraInfo.virtualTourUrl.trim();
|
||||
const hasVirtualTour = normalizedVirtualTourUrl.length > 0;
|
||||
const virtualTourEmbedPreview = hasVirtualTour ? getVirtualTourEmbed(normalizedVirtualTourUrl) : null;
|
||||
@@ -14573,7 +14592,56 @@ function AddListingPage({ editListingId }: { editListingId?: number }) {
|
||||
};
|
||||
}, [isEdit, editListingId]);
|
||||
|
||||
// Warunek przejscia z kroku "Szczegóły" dalej - te same wymagane pola, ktore i tak sprawdza
|
||||
// publikacja, tyle ze egzekwowane juz przy wyjsciu z pierwszej strony. Pusty string = brak bledu.
|
||||
const detailsStepError = (() => {
|
||||
if (!listingDetails.title.trim()) return 'Podaj tytuł ogłoszenia.';
|
||||
if (isRichTextEmpty(listingDetails.description)) return 'Dodaj opis ogłoszenia.';
|
||||
if (descriptionCharCount < MIN_DESCRIPTION_CHARS) {
|
||||
return `Opis musi mieć co najmniej ${MIN_DESCRIPTION_CHARS} znaków (obecnie ${descriptionCharCount}).`;
|
||||
}
|
||||
if (!(numericPrice > 0)) return isRentTransaction ? 'Podaj poprawny czynsz miesięczny.' : 'Podaj poprawną cenę.';
|
||||
if (!(numericArea > 0)) return 'Podaj poprawną powierzchnię.';
|
||||
if (showRoomFields) {
|
||||
const roomsValue = Number(listingDetails.rooms);
|
||||
if (!Number.isFinite(roomsValue) || roomsValue < 1) return 'Wybierz liczbę pokoi.';
|
||||
}
|
||||
if (contactPhoneError) return contactPhoneError;
|
||||
return '';
|
||||
})();
|
||||
|
||||
// Warunek przejscia z kroku "Lokalizacja" do zdjec - pola oznaczone gwiazdka na tej stronie.
|
||||
// Wyjatek: gdy mapa odnalazla konkretny budynek (locationResolved), pinezka wystarcza za ulice
|
||||
// i numer - uzytkownik mogl wpisac caly adres w jedno pole, a i tak wiemy gdzie jest oferta.
|
||||
const locationStepError = (() => {
|
||||
if (!location.city.trim()) return 'Podaj miasto.';
|
||||
if (locationResolved) return '';
|
||||
if (!location.street.trim()) return 'Podaj ulicę.';
|
||||
if (!location.building.trim()) return 'Podaj numer budynku.';
|
||||
return '';
|
||||
})();
|
||||
|
||||
const goToStep = (step: number) => {
|
||||
// Blokada wyjscia z pierwszej strony (currentStep 0 = "Szczegóły"), gdy brakuje wymaganych pol.
|
||||
// Dotyczy zarowno przycisku "Dalej", jak i skoku naprzod w bocznym stepperze; wstecz przepuszczamy.
|
||||
if (currentStep === 0 && step > 0 && detailsStepError) {
|
||||
setShowDetailsError(true);
|
||||
setContactPhoneTouched(true);
|
||||
window.requestAnimationFrame(() => {
|
||||
document.querySelector('.add-step-error')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Analogiczna blokada wyjscia z kroku "Lokalizacja" (currentStep 1) do zdjec i dalej.
|
||||
if (currentStep === 1 && step > 1 && locationStepError) {
|
||||
setShowLocationError(true);
|
||||
window.requestAnimationFrame(() => {
|
||||
document.querySelector('.add-step-error')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
});
|
||||
return;
|
||||
}
|
||||
setShowDetailsError(false);
|
||||
setShowLocationError(false);
|
||||
setCurrentStep(step);
|
||||
window.requestAnimationFrame(() => {
|
||||
document.querySelector('.add-page')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
@@ -14799,15 +14867,17 @@ function AddListingPage({ editListingId }: { editListingId?: number }) {
|
||||
const timer = window.setTimeout(() => {
|
||||
setLocationStatus('Szukam adresu na mapie...');
|
||||
geocodeListingLocation(location, controller.signal)
|
||||
.then((point) => {
|
||||
if (!point) {
|
||||
.then((result) => {
|
||||
if (!result) {
|
||||
setLocationResolved(false);
|
||||
setLocationStatus('Nie znaleziono dokładnego adresu. Przybliż mapę ręcznie.');
|
||||
return;
|
||||
}
|
||||
const nextCenter = clampCenter(point);
|
||||
const nextCenter = clampCenter(result.point);
|
||||
setLocationCenter(nextCenter);
|
||||
setLocationMapCenter(nextCenter);
|
||||
setLocationMapZoom(location.approximateLocation ? APPROXIMATE_LOCATION_ZOOM : locationZoom(location));
|
||||
setLocationResolved(result.precise);
|
||||
setLocationStatus(location.approximateLocation
|
||||
? 'Ustawiliśmy przybliżony obszar na podstawie wpisanego adresu.'
|
||||
: 'Pinezka została ustawiona na podstawie wpisanego adresu.');
|
||||
@@ -14816,6 +14886,7 @@ function AddListingPage({ editListingId }: { editListingId?: number }) {
|
||||
if (error instanceof DOMException && error.name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
setLocationResolved(false);
|
||||
setLocationStatus('Nie udało się pobrać lokalizacji. Spróbuj kliknąć punkt na mapie.');
|
||||
});
|
||||
}, 650);
|
||||
@@ -14876,9 +14947,14 @@ function AddListingPage({ editListingId }: { editListingId?: number }) {
|
||||
building: details.building ?? '',
|
||||
unit: '',
|
||||
}));
|
||||
// Trafienie w konkretny budynek (jest numer) znosi wymog recznego wpisania ulicy i numeru.
|
||||
setLocationResolved(Boolean(details.building));
|
||||
setLocationStatus('Pola lokalizacji zostały uzupełnione z mapy.');
|
||||
})
|
||||
.catch(() => setLocationStatus('Nie udało się odczytać adresu z mapy. Przesuń pinezkę bliżej budynku.'));
|
||||
.catch(() => {
|
||||
setLocationResolved(false);
|
||||
setLocationStatus('Nie udało się odczytać adresu z mapy. Przesuń pinezkę bliżej budynku.');
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -15048,6 +15124,12 @@ function AddListingPage({ editListingId }: { editListingId?: number }) {
|
||||
Twoje dane są bezpieczne. Nie udostępniamy ich osobom trzecim.
|
||||
</p>
|
||||
|
||||
{showDetailsError && detailsStepError && (
|
||||
<p className="add-field-error add-step-error" role="alert">
|
||||
Uzupełnij wymagane pola, aby przejść do lokalizacji: {detailsStepError}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="add-wizard-nav">
|
||||
<button className="add-nav-back" type="button" onClick={() => (isEdit ? navigate(-1) : setCategoryChosen(false))}>
|
||||
<Icon name="chevron" /> Wstecz
|
||||
@@ -15129,6 +15211,12 @@ function AddListingPage({ editListingId }: { editListingId?: number }) {
|
||||
{locationStatus}
|
||||
</p>
|
||||
|
||||
{showLocationError && locationStepError && (
|
||||
<p className="add-field-error add-step-error" role="alert">
|
||||
Uzupełnij wymagane pola, aby przejść do zdjęć: {locationStepError}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="add-location-actions">
|
||||
<button className="back-step-button" type="button" onClick={() => goToStep(0)}>
|
||||
Wstecz
|
||||
@@ -27072,9 +27160,9 @@ function ListingDetailPage({
|
||||
unit: '',
|
||||
approximateLocation: false,
|
||||
}, controller.signal)
|
||||
.then((point) => {
|
||||
if (point) {
|
||||
setResolvedMapCenter(clampCenter(point));
|
||||
.then((result) => {
|
||||
if (result) {
|
||||
setResolvedMapCenter(clampCenter(result.point));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
|
||||
@@ -4520,6 +4520,18 @@ a.city-card:hover {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.add-step-error {
|
||||
background: #fdecea;
|
||||
border: 1px solid #f5c2bd;
|
||||
border-radius: 10px;
|
||||
color: #b5342a;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
margin: 0 0 12px;
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
.add-nav-back {
|
||||
align-items: center;
|
||||
background: #ffffff;
|
||||
|
||||
Reference in New Issue
Block a user