feat(spots): photos on add, one-tap save, flat My Spots, immersive map - #5
Merged
Conversation
Mobile Spots UX overhaul (backend support already deployed).
Add Spot (add.tsx):
- Add photos from the gallery (multi-select) or camera before saving.
On submit: createSpot -> upload each photo -> set the first as the main
image. Photo upload failures are surfaced; if the spot deduped to one
already owned by someone else, it's saved to My Spots and the user's
photos are NOT attached to the foreign spot.
My Spots (index.tsx):
- Now a FLAT list of spots you CREATED ("Mine") + spots you SAVED
("Saved"), merged and de-duped (Mine wins). Named Collections preserved
behind a secondary toggle.
- Map selected-spot card: one-tap bookmark save/unsave; long-press opens
the named-list picker (AddToSpotListModal). Saved state is seeded once
from the server (getSavedSpots) and reconciled on focus — fixes the
optimistic-unsave bounce and un-seeded preview cards.
- Fullscreen map is now immersive: header/tabs/search/pills hide, controls
(incl. a filter button) and the selected card stay, safe-area respected.
Spot detail ([spotId].tsx):
- Bookmark wired: tap = save/unsave to My Spots, long-press = named lists.
- Photo gallery (userPhotos + googlePhotos) via expo-image.
- Owner-only Edit (inline) + Delete.
API (src/lib/api/spots.ts):
- saveSpot / unsaveSpot / getSavedSpots / isSpotSaved / deleteSpot /
uploadSpotPhoto (multipart via expo-file-system). No <Marker> added
(New-Arch crash constraint respected).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(spots+feed): tag a spot in posts, any-user photo contributions Feed spot-tagging: - Compose (media/upload.tsx): optional "Tag a spot" picker (debounced search) → sends spotId on the post. Posting without a spot is unchanged. - The feed list and single-post views show a tappable spot chip that opens the spot. (Backend now enriches post.spot on lists too.) Spot photo contributions (Google-Maps model): - Any logged-in user can add photos to any spot from the detail screen (gallery multi-select or camera) → uploads then refreshes the gallery. - Per-photo actions on user photos: Report (any user except the uploader) and Delete (uploader/admin). Google photos aren't reportable/deletable. - Photos are public immediately; the backend auto-hides one after 3 distinct reports (App-Store-compliant UGC moderation). API (src/lib/api): reportSpotPhoto / deleteSpotPhoto (photo key is encodeURIComponent'd — the S3 key contains a "/", verified against prod route matching); spotId on CreatePostData + spot on FeedPost. Fixes from code review: encoded photo-key (report/delete were 404-ing); gallery no longer double-renders the header photo; can't report your own photo; refresh after report so an auto-hidden photo disappears. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(spots map): stop the Fabric touch-registry crash + steadier markers Fixes the hard crash on the spots map: NSInternalInconsistencyException: Inconsistency between local and UIKit touch registries (RCTSurfaceTouchHandler, New Architecture / iOS 26) Root cause (RN #53303): the projected-overlay marker Pressables were UNMOUNTED mid-gesture — projectToScreen returned null for off-screen points and the whole marker tree re-rendered on every onRegionChange frame — so a marker with a live UIKit touch disappeared and desynced Fabric's touch registry. "A lot of data" (many markers crossing the cull boundary) made it near-certain. Fixes: - projectToScreenXY: never returns null for off-screen points; markers stay MOUNTED and are hidden via opacity + pointerEvents instead of unmounting. - Stable, position-derived cluster keys (supercluster's cluster_id is reassigned across queries → key churn → remount). - Marker onPress defers its animateToRegion to the next frame so re-projection can't move/unmount the pressed marker while the touch is finalizing. - Safety net: patch-package neutralizes the fatal RCTAssert in RCTSurfaceTouchHandler (the guard right below it already skips the stray touch) — turns any residual desync into a no-op instead of a crash. Deep on-device testing still required (see PR notes): iOS 26 physical device, dense data, aggressive pan/pinch + tap-during-gesture. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(spots map): Google-Maps-style search, drop the +/- zoom buttons - Remove the +/- zoom controls (pinch-to-zoom covers it) and put a search (magnifying-glass) button in their place. - Tapping it opens a full-screen search: as you type it queries Google Places (biased to the current viewport) AND our spots in parallel, grouped as "Spots" and "Places". Tap a spot to fly there and open its card; tap a place to fly the map to that location. Reachable in fullscreen where the top search bar is hidden. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(spots): show the top search bar in list view only The map view now has its own Google-Maps-style in-map search (the magnifying-glass control), so the search bar above the content is redundant there. Gate it on viewMode === 'list' so it only appears in the list view. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(spots map): show the bottom spot card only when a pin is selected Previously the bottom of the map always showed a horizontal preview of up to 5 spots when nothing was selected. Now the bottom card is hidden by default and appears only when you tap a pin; tapping elsewhere on the map clears the selection and hides it, and tapping another pin swaps it in. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Mobile Spots UX overhaul so adding, finding, and collecting spots is fast and intuitive. Backend support (owner edit/delete, one-tap save,
/saved, private-spot gating) is already deployed to prod (32b7d0c).What changed
Add a spot + photos — the add flow now supports photos from the gallery (multi-select) or the camera. On submit it creates the spot, uploads each photo, and sets the first as the main image. If the spot dedupes to one already owned by someone else, it's saved to My Spots and your photos aren't attached to the foreign spot. Failed uploads are surfaced, not silent.
"My Spots" is now a flat list = spots you created (badge "Mine") + spots you saved (badge "Saved"), merged and de-duped (Mine wins). The named Collections feature is preserved behind a secondary toggle.
One-tap save — on the map's selected-spot card and the spot detail, tap the bookmark to save/unsave to My Spots; long-press opens the named-list picker. Saved state is seeded once from the server and reconciled on focus.
Immersive full-screen map — the existing toggle now truly hides the chrome (header/tabs/search/pills), keeps the map controls + a filter button + the selected card, and respects the safe area. Filtering by sport/category (already present) is reachable in fullscreen.
Spot detail — photo gallery (user + Google photos), owner-only inline Edit and Delete.
Environment Variables
No environment variable changes.
Technical Impact
expo-image-picker@17was already installed and camera + photo-library permission strings already exist inapp.config.js, so this ships as a JS change (no new EAS build required for the picker itself, though a build refresh is recommended).POST/DELETE /api/spots/:id/save,GET /api/spots/saved, owner-gatedPUT/DELETE /:id, andGET /:idnow hides non-approved spots from non-owners.react-native-maps<Marker>was added (crashes under the New Architecture); markers stay on the projected-overlay pattern.Testing Evidence
npx tsc --noEmit— 0 errors (whole project)npx biome check .— 0 errorsbash scripts/check-prod-ready.sh— all checks passDeployment Notes
Merge to
v2-rebuild. Recommend a dev-client/EAS build to exercise the camera + gallery picker and the map on-device before a store build.Follow-ups (not blocking)
<Marker>to a center-crosshair (flagged in a code comment; left as-is to avoid destabilizing the working location step).