To start your Phoenix server:
- Run
mix setupto install and setup dependencies - Start Phoenix endpoint with
mix phx.serveror inside IEx withiex -S mix phx.server
Now you can visit localhost:4000 from your browser.
Ready to run in production? Please check our deployment guides.
- Official website: https://www.phoenixframework.org/
- Guides: https://hexdocs.pm/phoenix/overview.html
- Docs: https://hexdocs.pm/phoenix
- Forum: https://elixirforum.com/c/phoenix-forum
- Source: https://github.com/phoenixframework/phoenix
This project integrates MapLibre GL JS to provide a full-screen, interactive map in a Phoenix LiveView application.
- Add a Full-Screen Map Container
- In your LiveView template (e.g.,
map_live.html.heex):<div id="map" phx-hook="MapLibreMap" phx-update="ignore" class="fixed inset-0 w-screen h-screen z-0"> <!-- Map rendered by JS hook --> </div>
- In your LiveView template (e.g.,
- MapLibreMap JS Hook
- The hook in
assets/js/app.js:- Initializes the map with a vector tile source.
- Uses browser geolocation to center the map and add a blue marker for you.
- Sends your geolocation to the backend, which shares your location in real time with all connected users.
- Only users within a 2 mile radius of your current location are shown on the map.
- Efficient geohash windowing: The backend uses geohash partitioning (precision 6, ~1 mile cells) and subscribes to your cell and its 8 neighbors (3x3 window) for real-time presence updates.
- True proximity filtering: Even after geohash windowing, only users within a true 2-mile radius (using the Haversine formula) are shown, avoiding blocky/geohash-only artifacts.
- Real-time presence partitioning: User presence is tracked and broadcast per geohash cell, ensuring scalable and privacy-preserving updates.
- Markers for all nearby users are shown in real time:
- Your marker is always red and rendered on top.
- Other users' markers are assigned random, distinguishable colors.
- Markers are slightly offset so they don't overlap, even if users are at the same location.
- Click/tap any marker to bring it to the top.
- Adds navigation and geolocate controls.
- Ensures responsive resizing.
- The hook in
- When you load the map, your browser will ask for geolocation permission.
- If allowed, your location is sent to the backend and shared with all connected users in real time.
- You will only see users who are within a 2 mile (~3.2 km) radius of your current location.
- Efficient geohash windowing: The backend subscribes to your geohash cell and its 8 neighbors (3x3 window, precision 6, ~1 mile per cell) for presence updates.
- True proximity filtering: Even after windowing, only users within a true 2-mile radius (using the Haversine formula) are shown.
- If denied, the map defaults to New York City.
- All users' locations are updated live as they move or connect/disconnect.
- Markers are color-coded and offset for visibility; your marker is always red and on top.
If you are logged in, you will only ever see one marker for yourself, even if you open the map in multiple tabs or devices at the same time. This is because the system tracks users by their unique user id, not by browser tab or session.
- Geohash partitioning: The backend uses the geohash library to encode user locations at precision 6 (~1 mile cells).
- Windowing: Each user subscribes to their cell and its 8 neighbors for real-time updates.
- Presence partitioning: User presence is tracked per geohash cell, so only relevant users receive updates, improving privacy and scalability.
- Proximity filtering: After windowing, a Haversine distance check ensures only users within 2 miles are shown, avoiding blocky/geohash-only artifacts.
- Dependency: The backend now depends on the
geohashElixir package.
- Chats are now created only when the first message is sent, preventing empty chats from appearing in the chat list.
- Real-time updates for new chats and messages are handled via Phoenix PubSub and Ash.Notifier.PubSub.
- The chat modal opens in a "pending" state if no chat exists; the chat is created and the modal updates as soon as the first message is sent.
- All chat and message actions use Ash code interface functions for maintainability and best practices.
- The MapLive LiveView subscribes to all relevant chat topics and the user's chat list topic for robust real-time updates.
- New chats and messages appear instantly in the chat list and chat modal for both sender and recipient, without requiring a page refresh.
- Subscriptions are managed automatically as chat membership changes.
- The new
Heex3Web.ChatHelpersmodule provides robust helpers for:- Grouping chat messages by time (5 min gap, 30 min max group, day change)
- Formatting chat timestamps in a user-friendly, industry-standard way ("Today 11:03 AM", "Yesterday 1:05 PM", etc.)
- All formatting is timezone-aware and uses the user's device timezone (detected in JS and sent to LiveView)
- The app now detects the user's browser timezone and passes it to all chat components, ensuring all chat times are shown in the user's local time.
- This is handled via a new
TimezoneJS hook and LiveView event.
- Users can now upload a profile photo, which is stored in Cloudinary under the
heex3_profilefolder. - The
photo_urlattribute has been added to theuserstable and Ash resource. - Uploaded images are displayed in the profile view and edit profile modal, with instant preview using LiveView's
<.live_img_preview />component. - Cloudinary transformations are used for efficient image delivery and sizing.
To enable Cloudinary uploads, set the following environment variables (see .env.example):
CLOUDINARY_API_KEY=your_api_key_here
CLOUDINARY_SECRET=your_secret_here
CLOUDINARY_CLOUD_NAME=your_cloud_name_here
These can be set in a .env file for development, and are required in production.
The map now supports profile photos in markers with an efficient streaming approach:
- Immediate Display: SVG circles with user initials are shown instantly
- Map Rendering: Map displays immediately with placeholder markers
- Photo Streaming: Profile images load asynchronously and replace initials
- Circular SVG Generation: Fixed positioning and sizing for consistent 60px markers
- Canvas Processing: Uses HTML5 Canvas to handle CORS and convert images to base64
- Duplicate Prevention: Prevents "image already exists" errors with proper cleanup
- Error Handling: Falls back to SVG initials if profile image fails to load
- Bandwidth Optimization: Uses WebP format and 60px width for map markers
- URL Parsing: New
extract_public_id_from_url/1function for parsing Cloudinary URLs - Transformation Presets: Map markers use
ar_1:1,c_auto,g_auto,w_60/r_max/f_webptransformation - Presence Integration: Photo URLs are included in user presence data for real-time updates
The application now includes a comprehensive theme system with support for multiple DaisyUI themes:
- The
Heex3Web.ThemeHelpermodule provides robust theme management:default_theme/0: Returns the configured default theme ("garden")get_user_theme/1: Returns user's theme or default if not setget_socket_theme/1: Returns theme from LiveView socket assignsget_theme_gradient/1: Returns CSS gradient classes for theme backgrounds
- Supports all DaisyUI themes including light, dark, cupcake, cyberpunk, and more
- User themes are stored in the
ui_themefield of the users table - Theme changes are applied in real-time via LiveView events
- Fallback gradients are provided for unknown themes