From f3690a8b422de7ba2c266f8ca4aa096437381179 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 4 Jun 2026 16:43:04 +0000 Subject: [PATCH] 1.0.1 --- core/__pycache__/forms.cpython-311.pyc | Bin 7385 -> 7515 bytes core/__pycache__/views.cpython-311.pyc | Bin 11810 -> 11844 bytes core/forms.py | 4 +-- core/templates/core/index.html | 4 +-- .../core/property_form_location.html | 17 +++++++--- core/views.py | 4 +-- static/css/custom.css | 1 + static/js/pinboard.js | 32 +++++++++++++++--- staticfiles/css/custom.css | 1 + staticfiles/js/pinboard.js | 32 +++++++++++++++--- 10 files changed, 77 insertions(+), 18 deletions(-) diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc index 6990f960ed8c0d789b4d59592a5740cc8d89a94c..5ac251f2397de19670ce6f3cb1c77e38a3b68886 100644 GIT binary patch delta 900 zcmZ{h-%C_M6vub&Zu|R(?YipjmR>`M>#A&|`6GXAhOLk*S`ejHlXpzxx_6nqOSu&G z&{Gc)90DPG=qXG=W$jPseTg7iDd-`e3xuGj=3JFj>OOq#%$zgle9w12cW!n0-uQfO zi};q`*5BLM@V$1BRakSxovKYewq#wk!&k>iheLJF60y#PaP2bJPyKO^d&zdgt$MCl z6faCTuPqd6ug+(gtaCX_a|V-*875EXHBF|f%2_T~@aGK9n9;nqFWU^BXNtA#$!c_p z&FGrSc-c$yhCZdwWi@87LP(_sZJwiB9&jz70I7tShz*b?XtQ5at+1MjOY#IDC($$vCve`W4exQ;-=toc`O{ RJ*TUAjrH<7#aFRz@&{y2-p~L5 delta 700 zcmca@b<>h}IWI340}%ACP|S+h$h(kz@)W-HlM^|@RBv$?L?^N$pu3CgC;yX> z+#JHI!o+AYxsu<4(RA`AeiH>VpqyWkIY@;Kh)@6#@*o1N%WX27zywCy$%R~!lQ#&g zVRWAyD_9}y0F=MQQk+?ps>xI&1ybxj`Gw#Xd9W655FgDf7m%3CL@0p> yus6`W773DwoSZK0#~3+zo3w)_I4Hpm0!yJP|HWaGo1apelWJF#uvuQlh7kZo;i%I9 diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index c1cdde3e1849547354186e89b7f634761e391b0d..38663bd82b76a60202849b9a6fea4496b3e36cd6 100644 GIT binary patch delta 158 zcmZ1!b0mg$IWI340}#AfrI>YfBd@Hag%ObFn3AGUP?TSgT2xZ0kW{IVlb@Vel9`{U zkYA*bn37VIT3oE~s9}nNUut4eQl(#NaYSvnYmlIWI340}wQ>P|Vu4kylnSMF+@pOi58lE-flb%`4H($xlu!$;{7FC@9J= zNG&RS0`VP+Q8y+L#AMJyYyZm02XmHvj6}9 diff --git a/core/forms.py b/core/forms.py index 643b821..ce823f0 100644 --- a/core/forms.py +++ b/core/forms.py @@ -27,7 +27,7 @@ class PropertyLocationForm(BootstrapFormMixin, forms.ModelForm): model = PropertyEntry fields = ["address", "latitude", "longitude", "phone", "email", "listing_type"] widgets = { - "address": forms.TextInput(attrs={"placeholder": "Street, area, city"}), + "address": forms.TextInput(attrs={"placeholder": "Type or paste the full address", "autocomplete": "street-address", "data-manual-address": "true"}), "phone": forms.TextInput(attrs={"placeholder": "+34 600 000 000"}), "email": forms.EmailInput(attrs={"placeholder": "owner@example.com"}), "listing_type": forms.Select(), @@ -39,7 +39,7 @@ class PropertyLocationForm(BootstrapFormMixin, forms.ModelForm): latitude = cleaned.get("latitude") longitude = cleaned.get("longitude") if not address and (latitude is None or longitude is None): - raise forms.ValidationError("Add an address or allow location so this property can be placed on the pinboard.") + raise forms.ValidationError("Type or paste an address, or allow location, so this property can be placed on the pinboard.") return cleaned diff --git a/core/templates/core/index.html b/core/templates/core/index.html index 6cbc919..00e3a69 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -31,7 +31,7 @@ Live MVP

Public nearby list

{{ total_entries }} entries · {{ photo_entries }} photo uploads

- Add current location → + Add by location/address → @@ -47,7 +47,7 @@
📍

Pin by location

-

Grant browser location permission, then submit an address or GPS coordinates with optional contact details.

+

Grant browser location permission, or type/paste an address if permission is unavailable, then submit optional contact details.

Pin a property
diff --git a/core/templates/core/property_form_location.html b/core/templates/core/property_form_location.html index 02696d7..c44f476 100644 --- a/core/templates/core/property_form_location.html +++ b/core/templates/core/property_form_location.html @@ -4,11 +4,19 @@
-

Create from current location

+

Create from location or address

Add a property pin

-

Address or GPS is required. Contact details and sale/rental type help others recognize the listing.

- - Location not captured yet. +

Use your current GPS position when available, or type/paste the address manually if browser location is blocked, denied, or unsupported.

+
+ + +
+ Choose current location or enter the address below. +
{% csrf_token %} {% if form.non_field_errors %}
{{ form.non_field_errors }}
{% endif %} @@ -17,6 +25,7 @@
{{ field }} + {% if field.name == "address" %}
Required if you do not grant current-location access.
{% endif %} {% if field.help_text %}
{{ field.help_text }}
{% endif %} {% for error in field.errors %}
{{ error }}
{% endfor %}
diff --git a/core/views.py b/core/views.py index 2a31551..3df766b 100644 --- a/core/views.py +++ b/core/views.py @@ -103,8 +103,8 @@ def add_location_property(request): else: form = PropertyLocationForm() context = { - "page_title": "Add current-location property — NearbyNest", - "meta_description": "Add a property sighting from your current location, with optional contact details.", + "page_title": "Add property by location or address — NearbyNest", + "meta_description": "Add a property sighting from current GPS location or a typed/pasted address, with optional contact details.", "form": form, } return render(request, "core/property_form_location.html", context) diff --git a/static/css/custom.css b/static/css/custom.css index 4f41501..848271f 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -53,6 +53,7 @@ a { color: var(--nest-primary); text-decoration: none; } a:hover { color: var(-- .permission-wizard { display: grid; gap: 1rem; padding: 1rem; } .permission-step { display: grid; grid-template-columns: 1fr auto; gap: 1rem; align-items: center; padding: 1.2rem; border-radius: 1.4rem; background: rgba(255,255,255,.56); } .permission-step h2 { font-size: 1.25rem; margin: .3rem 0; } .status-text { color: var(--nest-muted); font-weight: 700; } .action-dock { display: flex; flex-wrap: wrap; justify-content: center; gap: .8rem; margin-top: 1.4rem; } .form-card { padding: clamp(1.2rem, 4vw, 2rem); } .form-card h1 { letter-spacing: -.055em; } +.location-choice-panel { display: flex; flex-wrap: wrap; gap: .75rem; align-items: center; } .manual-address-tip { padding: 1rem; border-radius: 1.25rem; background: rgba(215,255,241,.76); border: 1px solid rgba(15,118,110,.2); } .manual-address-tip span { display: inline-flex; margin-bottom: .35rem; color: var(--nest-primary); text-transform: uppercase; letter-spacing: .12em; font-size: .72rem; font-weight: 900; } .manual-address-tip strong { display: block; } .manual-address-tip p { margin: .25rem 0 0; color: var(--nest-muted); } .form-control, .form-select { border-radius: 1rem; border-color: rgba(20,33,61,.16); padding: .82rem 1rem; } .form-control:focus, .form-select:focus { border-color: var(--nest-primary); box-shadow: 0 0 0 .25rem rgba(15,118,110,.12); } .invalid-copy { color: #b42318; font-weight: 700; font-size: .9rem; margin-top: .35rem; } .list-hero { display: flex; justify-content: space-between; align-items: end; gap: 1.5rem; margin-bottom: 1.25rem; } .sort-panel { display: flex; flex-wrap: wrap; gap: .8rem; align-items: end; padding: 1rem; margin-bottom: 1rem; } .sort-panel > div { min-width: min(100%, 16rem); } diff --git a/static/js/pinboard.js b/static/js/pinboard.js index aa801da..d2a825a 100644 --- a/static/js/pinboard.js +++ b/static/js/pinboard.js @@ -3,9 +3,25 @@ function setText(id, text) { if (el) el.textContent = text; } -function requestLocation(callback, statusId) { +function showManualAddressTip(message) { + const tip = document.querySelector("[data-manual-address-tip]"); + const addressInput = document.querySelector("[data-manual-address]") || document.getElementById("id_address"); + if (tip) { + tip.hidden = false; + } + if (message) { + setText("form-location-status", message); + } + if (addressInput) { + addressInput.focus({ preventScroll: true }); + addressInput.scrollIntoView({ behavior: "smooth", block: "center" }); + } +} + +function requestLocation(callback, statusId, unavailableCallback) { if (!navigator.geolocation) { - setText(statusId, "Location is not supported by this browser."); + setText(statusId, "Location is not supported by this browser. You can type or paste the address instead."); + if (unavailableCallback) unavailableCallback(); return; } setText(statusId, "Requesting location…"); @@ -16,7 +32,10 @@ function requestLocation(callback, statusId) { setText(statusId, `Captured ${lat}, ${lng}`); callback(lat, lng); }, - () => setText(statusId, "Location permission was denied or unavailable."), + () => { + setText(statusId, "Location permission was denied or unavailable. Type or paste the address instead."); + if (unavailableCallback) unavailableCallback(); + }, { enableHighAccuracy: true, timeout: 10000 } ); } @@ -35,13 +54,18 @@ document.addEventListener("click", (event) => { setText("notification-status", `Notification permission: ${permission}`); }); } + if (action === "manual-address") { + showManualAddressTip("Manual address mode: paste or type the property address below."); + } if (action === "fill-current-location") { requestLocation((lat, lng) => { const latInput = document.getElementById("id_latitude"); const lngInput = document.getElementById("id_longitude"); if (latInput) latInput.value = lat; if (lngInput) lngInput.value = lng; - }, "form-location-status"); + }, "form-location-status", () => { + showManualAddressTip(); + }); } if (action === "use-location-for-list") { requestLocation((lat, lng) => { diff --git a/staticfiles/css/custom.css b/staticfiles/css/custom.css index 4f41501..848271f 100644 --- a/staticfiles/css/custom.css +++ b/staticfiles/css/custom.css @@ -53,6 +53,7 @@ a { color: var(--nest-primary); text-decoration: none; } a:hover { color: var(-- .permission-wizard { display: grid; gap: 1rem; padding: 1rem; } .permission-step { display: grid; grid-template-columns: 1fr auto; gap: 1rem; align-items: center; padding: 1.2rem; border-radius: 1.4rem; background: rgba(255,255,255,.56); } .permission-step h2 { font-size: 1.25rem; margin: .3rem 0; } .status-text { color: var(--nest-muted); font-weight: 700; } .action-dock { display: flex; flex-wrap: wrap; justify-content: center; gap: .8rem; margin-top: 1.4rem; } .form-card { padding: clamp(1.2rem, 4vw, 2rem); } .form-card h1 { letter-spacing: -.055em; } +.location-choice-panel { display: flex; flex-wrap: wrap; gap: .75rem; align-items: center; } .manual-address-tip { padding: 1rem; border-radius: 1.25rem; background: rgba(215,255,241,.76); border: 1px solid rgba(15,118,110,.2); } .manual-address-tip span { display: inline-flex; margin-bottom: .35rem; color: var(--nest-primary); text-transform: uppercase; letter-spacing: .12em; font-size: .72rem; font-weight: 900; } .manual-address-tip strong { display: block; } .manual-address-tip p { margin: .25rem 0 0; color: var(--nest-muted); } .form-control, .form-select { border-radius: 1rem; border-color: rgba(20,33,61,.16); padding: .82rem 1rem; } .form-control:focus, .form-select:focus { border-color: var(--nest-primary); box-shadow: 0 0 0 .25rem rgba(15,118,110,.12); } .invalid-copy { color: #b42318; font-weight: 700; font-size: .9rem; margin-top: .35rem; } .list-hero { display: flex; justify-content: space-between; align-items: end; gap: 1.5rem; margin-bottom: 1.25rem; } .sort-panel { display: flex; flex-wrap: wrap; gap: .8rem; align-items: end; padding: 1rem; margin-bottom: 1rem; } .sort-panel > div { min-width: min(100%, 16rem); } diff --git a/staticfiles/js/pinboard.js b/staticfiles/js/pinboard.js index aa801da..d2a825a 100644 --- a/staticfiles/js/pinboard.js +++ b/staticfiles/js/pinboard.js @@ -3,9 +3,25 @@ function setText(id, text) { if (el) el.textContent = text; } -function requestLocation(callback, statusId) { +function showManualAddressTip(message) { + const tip = document.querySelector("[data-manual-address-tip]"); + const addressInput = document.querySelector("[data-manual-address]") || document.getElementById("id_address"); + if (tip) { + tip.hidden = false; + } + if (message) { + setText("form-location-status", message); + } + if (addressInput) { + addressInput.focus({ preventScroll: true }); + addressInput.scrollIntoView({ behavior: "smooth", block: "center" }); + } +} + +function requestLocation(callback, statusId, unavailableCallback) { if (!navigator.geolocation) { - setText(statusId, "Location is not supported by this browser."); + setText(statusId, "Location is not supported by this browser. You can type or paste the address instead."); + if (unavailableCallback) unavailableCallback(); return; } setText(statusId, "Requesting location…"); @@ -16,7 +32,10 @@ function requestLocation(callback, statusId) { setText(statusId, `Captured ${lat}, ${lng}`); callback(lat, lng); }, - () => setText(statusId, "Location permission was denied or unavailable."), + () => { + setText(statusId, "Location permission was denied or unavailable. Type or paste the address instead."); + if (unavailableCallback) unavailableCallback(); + }, { enableHighAccuracy: true, timeout: 10000 } ); } @@ -35,13 +54,18 @@ document.addEventListener("click", (event) => { setText("notification-status", `Notification permission: ${permission}`); }); } + if (action === "manual-address") { + showManualAddressTip("Manual address mode: paste or type the property address below."); + } if (action === "fill-current-location") { requestLocation((lat, lng) => { const latInput = document.getElementById("id_latitude"); const lngInput = document.getElementById("id_longitude"); if (latInput) latInput.value = lat; if (lngInput) lngInput.value = lng; - }, "form-location-status"); + }, "form-location-status", () => { + showManualAddressTip(); + }); } if (action === "use-location-for-list") { requestLocation((lat, lng) => {