Merge pull request #1905 from Freika/dev

0.34.1
This commit is contained in:
Evgenii Burmakin 2025-10-30 20:01:09 +01:00 committed by GitHub
commit 55e1f4a161
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 468 additions and 203 deletions

View file

@ -1 +1 @@
0.34.0
0.34.1

View file

@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
# [0.34.1] - 2025-10-30
## Fixed
- Broken Stats page for users with no reverse geocoding enabled. #1877
## Changed
- Date navigation on the map page is no longer shown as floating panel. It is now part of the top navigation bar to prevent overlapping with other map controls. #1894 #1881
## Added
- [Dawarich Cloud] Added support for UTM parameters during user registration. UTM parameters will be stored with the user record for marketing analytics purposes.
# [0.34.0] - 2025-10-10
## The Family release

24
Gemfile
View file

@ -28,8 +28,8 @@ gem 'parallel'
gem 'pg'
gem 'prometheus_exporter'
gem 'puma'
gem 'pundit'
gem 'rails', '~> 8.0'
gem 'pundit', '>= 2.5.1'
gem 'rails', '~> 8.0', '>= 8.0.3'
gem 'rails_icons'
gem 'redis'
gem 'rexml'
@ -39,18 +39,18 @@ gem 'rgeo-geojson'
gem 'rqrcode', '~> 3.0'
gem 'rswag-api'
gem 'rswag-ui'
gem 'rubyzip', '~> 3.1'
gem 'sentry-rails'
gem 'rubyzip', '~> 3.2'
gem 'sentry-rails', '>= 5.27.0'
gem 'sentry-ruby'
gem 'sidekiq'
gem 'sidekiq-cron'
gem 'sidekiq', '>= 8.0.5'
gem 'sidekiq-cron', '>= 2.3.1'
gem 'sidekiq-limit_fetch'
gem 'sprockets-rails'
gem 'stackprof'
gem 'stimulus-rails'
gem 'strong_migrations'
gem 'tailwindcss-rails'
gem 'turbo-rails'
gem 'strong_migrations', '>= 2.4.0'
gem 'tailwindcss-rails', '= 3.3.2'
gem 'turbo-rails', '>= 2.0.17'
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
group :development, :test, :staging do
@ -62,7 +62,7 @@ group :development, :test, :staging do
gem 'ffaker'
gem 'pry-byebug'
gem 'pry-rails'
gem 'rspec-rails'
gem 'rspec-rails', '>= 8.0.1'
gem 'rswag-specs'
end
@ -77,7 +77,7 @@ group :test do
end
group :development do
gem 'database_consistency', require: false
gem 'database_consistency', '>= 2.0.5', require: false
gem 'foreman'
gem 'rubocop-rails', require: false
gem 'rubocop-rails', '>= 2.33.4', require: false
end

View file

@ -10,29 +10,29 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (8.0.2.1)
actionpack (= 8.0.2.1)
activesupport (= 8.0.2.1)
actioncable (8.0.3)
actionpack (= 8.0.3)
activesupport (= 8.0.3)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (8.0.2.1)
actionpack (= 8.0.2.1)
activejob (= 8.0.2.1)
activerecord (= 8.0.2.1)
activestorage (= 8.0.2.1)
activesupport (= 8.0.2.1)
actionmailbox (8.0.3)
actionpack (= 8.0.3)
activejob (= 8.0.3)
activerecord (= 8.0.3)
activestorage (= 8.0.3)
activesupport (= 8.0.3)
mail (>= 2.8.0)
actionmailer (8.0.2.1)
actionpack (= 8.0.2.1)
actionview (= 8.0.2.1)
activejob (= 8.0.2.1)
activesupport (= 8.0.2.1)
actionmailer (8.0.3)
actionpack (= 8.0.3)
actionview (= 8.0.3)
activejob (= 8.0.3)
activesupport (= 8.0.3)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (8.0.2.1)
actionview (= 8.0.2.1)
activesupport (= 8.0.2.1)
actionpack (8.0.3)
actionview (= 8.0.3)
activesupport (= 8.0.3)
nokogiri (>= 1.8.5)
rack (>= 2.2.4)
rack-session (>= 1.0.1)
@ -40,38 +40,38 @@ GEM
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
actiontext (8.0.2.1)
actionpack (= 8.0.2.1)
activerecord (= 8.0.2.1)
activestorage (= 8.0.2.1)
activesupport (= 8.0.2.1)
actiontext (8.0.3)
actionpack (= 8.0.3)
activerecord (= 8.0.3)
activestorage (= 8.0.3)
activesupport (= 8.0.3)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (8.0.2.1)
activesupport (= 8.0.2.1)
actionview (8.0.3)
activesupport (= 8.0.3)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
activejob (8.0.2.1)
activesupport (= 8.0.2.1)
activejob (8.0.3)
activesupport (= 8.0.3)
globalid (>= 0.3.6)
activemodel (8.0.2.1)
activesupport (= 8.0.2.1)
activerecord (8.0.2.1)
activemodel (= 8.0.2.1)
activesupport (= 8.0.2.1)
activemodel (8.0.3)
activesupport (= 8.0.3)
activerecord (8.0.3)
activemodel (= 8.0.3)
activesupport (= 8.0.3)
timeout (>= 0.4.0)
activerecord-postgis-adapter (11.0.0)
activerecord (~> 8.0.0)
rgeo-activerecord (~> 8.0.0)
activestorage (8.0.2.1)
actionpack (= 8.0.2.1)
activejob (= 8.0.2.1)
activerecord (= 8.0.2.1)
activesupport (= 8.0.2.1)
activestorage (8.0.3)
actionpack (= 8.0.3)
activejob (= 8.0.3)
activerecord (= 8.0.3)
activesupport (= 8.0.3)
marcel (~> 1.0)
activesupport (8.0.2.1)
activesupport (8.0.3)
base64
benchmark (>= 0.3)
bigdecimal
@ -142,7 +142,7 @@ GEM
data_migrate (11.3.0)
activerecord (>= 6.1)
railties (>= 6.1)
database_consistency (2.0.4)
database_consistency (2.0.6)
activerecord (>= 3.2)
date (3.4.1)
debug (1.11.0)
@ -163,7 +163,7 @@ GEM
drb (2.2.3)
erb (5.0.2)
erubi (1.13.1)
et-orbi (1.2.11)
et-orbi (1.4.0)
tzinfo
factory_bot (6.5.5)
activesupport (>= 6.1.0)
@ -180,10 +180,10 @@ GEM
ffi (1.17.2-x86_64-linux-gnu)
foreman (0.90.0)
thor (~> 1.4)
fugit (1.11.1)
et-orbi (~> 1, >= 1.2.11)
fugit (1.12.1)
et-orbi (~> 1.4)
raabro (~> 1.4)
globalid (1.2.1)
globalid (1.3.0)
activesupport (>= 6.1)
gpx (1.2.1)
csv
@ -212,7 +212,7 @@ GEM
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jmespath (1.6.2)
json (2.13.2)
json (2.15.0)
json-schema (5.0.1)
addressable (~> 2.8)
jwt (2.10.1)
@ -240,12 +240,13 @@ GEM
loofah (2.24.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
mail (2.9.0)
logger
mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
marcel (1.0.4)
marcel (1.1.0)
matrix (0.4.2)
method_source (1.1.0)
mini_mime (1.1.5)
@ -255,7 +256,7 @@ GEM
multi_json (1.15.0)
multi_xml (0.7.1)
bigdecimal (~> 3.1)
net-imap (0.5.9)
net-imap (0.5.12)
date
net-protocol
net-pop (0.1.2)
@ -265,18 +266,18 @@ GEM
net-smtp (0.5.1)
net-protocol
nio4r (2.7.4)
nokogiri (1.18.9)
nokogiri (1.18.10)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.18.9-aarch64-linux-gnu)
nokogiri (1.18.10-aarch64-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.9-arm-linux-gnu)
nokogiri (1.18.10-arm-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.9-arm64-darwin)
nokogiri (1.18.10-arm64-darwin)
racc (~> 1.4)
nokogiri (1.18.9-x86_64-darwin)
nokogiri (1.18.10-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.18.9-x86_64-linux-gnu)
nokogiri (1.18.10-x86_64-linux-gnu)
racc (~> 1.4)
oj (3.16.11)
bigdecimal (>= 3.0)
@ -313,13 +314,13 @@ GEM
date
stringio
public_suffix (6.0.1)
puma (6.6.1)
puma (7.1.0)
nio4r (~> 2.0)
pundit (2.5.0)
pundit (2.5.2)
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.8.1)
rack (3.2.1)
rack (3.2.2)
rack-session (2.1.1)
base64 (>= 0.1.0)
rack (>= 3.0.0)
@ -327,20 +328,20 @@ GEM
rack (>= 1.3)
rackup (2.2.1)
rack (>= 3)
rails (8.0.2.1)
actioncable (= 8.0.2.1)
actionmailbox (= 8.0.2.1)
actionmailer (= 8.0.2.1)
actionpack (= 8.0.2.1)
actiontext (= 8.0.2.1)
actionview (= 8.0.2.1)
activejob (= 8.0.2.1)
activemodel (= 8.0.2.1)
activerecord (= 8.0.2.1)
activestorage (= 8.0.2.1)
activesupport (= 8.0.2.1)
rails (8.0.3)
actioncable (= 8.0.3)
actionmailbox (= 8.0.3)
actionmailer (= 8.0.3)
actionpack (= 8.0.3)
actiontext (= 8.0.3)
actionview (= 8.0.3)
activejob (= 8.0.3)
activemodel (= 8.0.3)
activerecord (= 8.0.3)
activestorage (= 8.0.3)
activesupport (= 8.0.3)
bundler (>= 1.15.0)
railties (= 8.0.2.1)
railties (= 8.0.3)
rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
@ -351,13 +352,14 @@ GEM
rails_icons (1.4.0)
nokogiri (~> 1.16, >= 1.16.4)
rails (> 6.1)
railties (8.0.2.1)
actionpack (= 8.0.2.1)
activesupport (= 8.0.2.1)
railties (8.0.3)
actionpack (= 8.0.3)
activesupport (= 8.0.3)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0, >= 1.2.2)
tsort (>= 0.2)
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.3.0)
@ -368,7 +370,7 @@ GEM
redis-client (>= 0.22.0)
redis-client (0.24.0)
connection_pool
regexp_parser (2.11.2)
regexp_parser (2.11.3)
reline (0.6.2)
io-console (~> 0.5)
request_store (1.7.0)
@ -390,13 +392,13 @@ GEM
rqrcode_core (2.0.0)
rspec-core (3.13.3)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.4)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.4)
rspec-mocks (3.13.6)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-rails (8.0.0)
rspec-rails (8.0.2)
actionpack (>= 7.2)
activesupport (>= 7.2)
railties (>= 7.2)
@ -416,7 +418,7 @@ GEM
rswag-ui (2.16.0)
actionpack (>= 5.2, < 8.1)
railties (>= 5.2, < 8.1)
rubocop (1.80.2)
rubocop (1.81.1)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@ -424,20 +426,20 @@ GEM
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.46.0, < 2.0)
rubocop-ast (>= 1.47.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.46.0)
rubocop-ast (1.47.1)
parser (>= 3.3.7.2)
prism (~> 1.4)
rubocop-rails (2.33.3)
rubocop-rails (2.33.4)
activesupport (>= 4.2.0)
lint_roller (~> 1.1)
rack (>= 1.1)
rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.44.0, < 2.0)
ruby-progressbar (1.13.0)
rubyzip (3.1.0)
rubyzip (3.2.0)
securerandom (0.4.1)
selenium-webdriver (4.35.0)
base64 (~> 0.2)
@ -445,21 +447,21 @@ GEM
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 4.0)
websocket (~> 1.0)
sentry-rails (5.26.0)
sentry-rails (5.28.0)
railties (>= 5.0)
sentry-ruby (~> 5.26.0)
sentry-ruby (5.26.0)
sentry-ruby (~> 5.28.0)
sentry-ruby (5.28.0)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
shoulda-matchers (6.5.0)
activesupport (>= 5.2.0)
sidekiq (8.0.4)
sidekiq (8.0.8)
connection_pool (>= 2.5.0)
json (>= 2.9.0)
logger (>= 1.6.2)
rack (>= 3.1.0)
redis-client (>= 0.23.2)
sidekiq-cron (2.3.0)
sidekiq-cron (2.3.1)
cronex (>= 0.13.0)
fugit (~> 1.8, >= 1.11.1)
globalid (>= 1.0.1)
@ -483,13 +485,13 @@ GEM
stimulus-rails (1.3.4)
railties (>= 6.0.0)
stringio (3.1.7)
strong_migrations (2.3.0)
activerecord (>= 7)
strong_migrations (2.5.1)
activerecord (>= 7.1)
super_diff (0.16.0)
attr_extras (>= 6.2.4)
diff-lcs
patience_diff
tailwindcss-rails (3.3.1)
tailwindcss-rails (3.3.2)
railties (>= 7.0.0)
tailwindcss-ruby (~> 3.0)
tailwindcss-ruby (3.4.17)
@ -500,7 +502,8 @@ GEM
tailwindcss-ruby (3.4.17-x86_64-linux)
thor (1.4.0)
timeout (0.4.3)
turbo-rails (2.0.16)
tsort (0.2.0)
turbo-rails (2.0.17)
actionpack (>= 7.1.0)
railties (>= 7.1.0)
tzinfo (2.0.6)
@ -546,7 +549,7 @@ DEPENDENCIES
capybara
chartkick
data_migrate
database_consistency
database_consistency (>= 2.0.5)
debug
devise
dotenv-rails
@ -570,8 +573,8 @@ DEPENDENCIES
pry-byebug
pry-rails
puma
pundit
rails (~> 8.0)
pundit (>= 2.5.1)
rails (~> 8.0, >= 8.0.3)
rails_icons
redis
rexml
@ -579,27 +582,27 @@ DEPENDENCIES
rgeo-activerecord
rgeo-geojson
rqrcode (~> 3.0)
rspec-rails
rspec-rails (>= 8.0.1)
rswag-api
rswag-specs
rswag-ui
rubocop-rails
rubyzip (~> 3.1)
rubocop-rails (>= 2.33.4)
rubyzip (~> 3.2)
selenium-webdriver
sentry-rails
sentry-rails (>= 5.27.0)
sentry-ruby
shoulda-matchers
sidekiq
sidekiq-cron
sidekiq (>= 8.0.5)
sidekiq-cron (>= 2.3.1)
sidekiq-limit_fetch
simplecov
sprockets-rails
stackprof
stimulus-rails
strong_migrations
strong_migrations (>= 2.4.0)
super_diff
tailwindcss-rails
turbo-rails
tailwindcss-rails (= 3.3.2)
turbo-rails (>= 2.0.17)
tzinfo-data
webmock

View file

@ -8,14 +8,17 @@
## 📸 Screenshots
![Map](screenshots/map.jpeg)
![Map](screenshots/map.png)
*Map View*
![Stats](screenshots/stats.jpeg)
![Family](screenshots/family.png)
*Family Page*
![Stats](screenshots/stats.png)
*Statistics Overview*
![Import](screenshots/imports.jpeg)
*Imports page*
![Trips](screenshots/trips.png)
*Trips page*
---
@ -28,6 +31,9 @@ It enables you to:
- Track your location history.
- Visualize your data on an interactive map.
- Create trips and analyze your travel history.
- Share your location with family members.
- Integrate with photo management apps like Immich and Photoprism to visualize geotagged photos.
- Import your location history from Google Maps Timeline, OwnTracks, GPX, GeoJSON and some other sources
- Explore statistics like the number of countries and cities visited, total distance traveled, and more!
@ -99,6 +105,11 @@ Feel free to change them in the account settings.
- Lines between points
- Fog of War
### 👪 Family Sharing
- Share your location with family members.
- View locations of family members on the map (with their consent).
- Each family member can enable or disable location sharing individually.
### 🔵 Areas
- Draw areas on the map so Dawarich could suggest your visits there.
@ -109,7 +120,6 @@ Feel free to change them in the account settings.
- Analyze your travel history: number of countries/cities visited, distance traveled, and time spent, broken down by year and month.
### ✈️ Trips
- Create a trip to visualize your travels between two points in time. You'll be able to see the route, distance, and time spent, and also add notes to your trip. If you have Immich or Photoprism integration, you'll also be able to see photos from your trips!
### 📸 Integrations

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,33 @@
# frozen_string_literal: true
extend ActiveSupport::Concern
UTM_PARAMS = %w[utm_source utm_medium utm_campaign utm_term utm_content].freeze
def store_utm_params
UTM_PARAMS.each do |param|
session[param] = params[param] if params[param].present?
end
end
def assign_utm_params(record)
utm_data = extract_utm_data_from_session
return unless utm_data.any?
record.update_columns(utm_data)
clear_utm_session
end
private
def extract_utm_data_from_session
UTM_PARAMS.each_with_object({}) do |param, hash|
hash[param] = session[param] if session[param].present?
end
end
def clear_utm_session
UTM_PARAMS.each { |param| session.delete(param) }
end
end

View file

@ -1,8 +1,11 @@
# frozen_string_literal: true
class Users::RegistrationsController < Devise::RegistrationsController
include UtmTrackable
before_action :set_invitation, only: %i[new create]
before_action :check_registration_allowed, only: %i[new create]
before_action :store_utm_params, only: %i[new], unless: -> { DawarichSettings.self_hosted? }
def new
build_resource({})
@ -16,8 +19,9 @@ class Users::RegistrationsController < Devise::RegistrationsController
def create
super do |resource|
if resource.persisted? && @invitation
accept_invitation_for_user(resource)
if resource.persisted?
assign_utm_params(resource)
accept_invitation_for_user(resource) if @invitation
end
end
end
@ -80,11 +84,13 @@ class Users::RegistrationsController < Devise::RegistrationsController
if service.call
flash[:notice] = "Welcome to #{@invitation.family.name}! You're now part of the family."
else
flash[:alert] = "Account created successfully, but there was an issue accepting the invitation: #{service.error_message}"
flash[:alert] =
"Account created successfully, but there was an issue accepting the invitation: #{service.error_message}"
end
rescue StandardError => e
Rails.logger.error "Error accepting invitation during registration: #{e.message}"
flash[:alert] = "Account created successfully, but there was an issue accepting the invitation. Please try accepting it again."
flash[:alert] =
'Account created successfully, but there was an issue accepting the invitation. Please try accepting it again.'
end
def sign_up_params

View file

@ -122,9 +122,8 @@ export default class extends BaseController {
});
});
// Add markers and route
// Add route (no markers on trip forms)
if (this.coordinates?.length > 0) {
this.addMarkers()
this.addPolyline()
this.fitMapToBounds()
}
@ -246,9 +245,8 @@ export default class extends BaseController {
this.polylinesLayer.clearLayers()
this.photoMarkers.clearLayers()
// Add new markers and route if coordinates exist
// Add only polyline (no markers) when coordinates exist
if (this.coordinates?.length > 0) {
this.addMarkers()
this.addPolyline()
this.fitMapToBounds()
}

View file

@ -1,30 +1,30 @@
export const mapsConfig = {
"Light": {
url: "https://tyles.dwri.xyz/20250420/{z}/{x}/{y}.mvt",
url: "https://tyles.dwri.xyz/planet/{z}/{x}/{y}.mvt",
flavor: "light",
maxZoom: 16,
attribution: "<a href='https://github.com/protomaps/basemaps'>Protomaps</a>, &copy; <a href='https://openstreetmap.org'>OpenStreetMap</a>"
},
"Dark": {
url: "https://tyles.dwri.xyz/20250420/{z}/{x}/{y}.mvt",
url: "https://tyles.dwri.xyz/planet/{z}/{x}/{y}.mvt",
flavor: "dark",
maxZoom: 16,
attribution: "<a href='https://github.com/protomaps/basemaps'>Protomaps</a>, &copy; <a href='https://openstreetmap.org'>OpenStreetMap</a>"
},
"White": {
url: "https://tyles.dwri.xyz/20250420/{z}/{x}/{y}.mvt",
url: "https://tyles.dwri.xyz/planet/{z}/{x}/{y}.mvt",
flavor: "white",
maxZoom: 16,
attribution: "<a href='https://github.com/protomaps/basemaps'>Protomaps</a>, &copy; <a href='https://openstreetmap.org'>OpenStreetMap</a>"
},
"Grayscale": {
url: "https://tyles.dwri.xyz/20250420/{z}/{x}/{y}.mvt",
url: "https://tyles.dwri.xyz/planet/{z}/{x}/{y}.mvt",
flavor: "grayscale",
maxZoom: 16,
attribution: "<a href='https://github.com/protomaps/basemaps'>Protomaps</a>, &copy; <a href='https://openstreetmap.org'>OpenStreetMap</a>"
},
"Black": {
url: "https://tyles.dwri.xyz/20250420/{z}/{x}/{y}.mvt",
url: "https://tyles.dwri.xyz/planet/{z}/{x}/{y}.mvt",
flavor: "black",
maxZoom: 16,
attribution: "<a href='https://github.com/protomaps/basemaps'>Protomaps</a>, &copy; <a href='https://openstreetmap.org'>OpenStreetMap</a>"

View file

@ -1,3 +1,5 @@
<% content_for :title, "Editing Family" %>
<div class="container mx-auto px-4 py-8">
<div class="max-w-2xl mx-auto">
<div class="bg-base-200 rounded-lg p-6">

View file

@ -1,3 +1,5 @@
<% content_for :title, "Family Management" %>
<div class="container mx-auto px-4 py-8">
<div class="max-w-2xl mx-auto">
<div class="text-center mb-8">

View file

@ -1,3 +1,5 @@
<% content_for :title, "New Family" %>
<div class="container mx-auto px-4 py-8">
<div class="max-w-2xl mx-auto">
<div class="text-center mb-8">

View file

@ -1,3 +1,5 @@
<% content_for :title, "Family Details" %>
<div class="container mx-auto px-4 py-8">
<div class="max-w-4xl mx-auto">
<!-- Family Header -->

View file

@ -38,7 +38,7 @@
</div>
<!-- Full Screen Map Container -->
<div class='absolute top-16 left-0 right-0 w-full z-20' style='height: calc(100vh - 4rem);'>
<div class='absolute top-16 left-0 right-0 bottom-0 w-full z-20 flex flex-col'>
<%= yield %>
</div>

View file

@ -1,10 +1,9 @@
<% content_for :title, 'Map' %>
<!-- Floating Date Navigation Controls -->
<div class="fixed top-20 left-0 right-0 flex justify-center" style="z-index: 9999; margin-left: 80px; margin-right: 80px;">
<div style="width: 1500px; max-width: 100%;" data-controller="map-controls">
<!-- Date Navigation Controls - Native Page Element -->
<div class="w-full px-4 py-3 bg-base-100" data-controller="map-controls">
<!-- Mobile: Compact Toggle Button -->
<div class="lg:hidden justify-center flex">
<div class="lg:hidden flex justify-center">
<button
type="button"
data-action="click->map-controls#toggle"
@ -19,7 +18,7 @@
<!-- Expandable Panel (hidden on mobile by default, always visible on desktop) -->
<div
data-map-controls-target="panel"
class="hidden lg:!block bg-base-100 bg-opacity-95 rounded-lg shadow-lg p-4 mt-2 lg:mt-0 scale-80">
class="hidden lg:!block bg-base-100 rounded-lg shadow-lg p-4 mt-2 lg:mt-0">
<%= form_with url: map_path(import_id: params[:import_id]), method: :get do |f| %>
<div class="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4 lg:items-end">
<div class="w-full lg:w-1/12">
@ -72,12 +71,12 @@
<% end %>
</div>
</div>
</div>
<!-- Full Screen Map -->
<!-- Map Container - Fills remaining space -->
<div class="w-full h-full">
<div
id='map'
class="absolute inset-0 w-full h-full z-0"
class="w-full h-full"
data-controller="maps points add-visit family-members"
data-points-target="map"
data-api_key="<%= current_user.api_key %>"
@ -96,5 +95,6 @@
<div id="fog" class="fog"></div>
</div>
</div>
</div>
<%= render 'map/settings_modals' %>

View file

@ -18,6 +18,8 @@
<% if DawarichSettings.reverse_geocoding_enabled? %>
<%= render 'stats/reverse_geocoding_stats' %>
<% else %>
</div>
<% end %>
<div class='text-xs text-gray-500 text-center mt-5'>

View file

@ -0,0 +1,11 @@
# frozen_string_literal: true
class AddUtmParametersToUsers < ActiveRecord::Migration[8.0]
def change
add_column :users, :utm_source, :string
add_column :users, :utm_medium, :string
add_column :users, :utm_campaign, :string
add_column :users, :utm_term, :string
add_column :users, :utm_content, :string
end
end

33
db/schema.rb generated
View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_09_30_150256) do
ActiveRecord::Schema[8.0].define(version: 2025_10_30_190924) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
enable_extension "postgis"
@ -113,10 +113,10 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_30_150256) do
t.integer "status", default: 0, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_family_invitations_on_email"
t.index ["expires_at"], name: "index_family_invitations_on_expires_at"
t.index ["family_id"], name: "index_family_invitations_on_family_id"
t.index ["status"], name: "index_family_invitations_on_status"
t.index ["family_id", "email"], name: "index_family_invitations_on_family_id_and_email"
t.index ["family_id", "status", "expires_at"], name: "index_family_invitations_on_family_status_expires"
t.index ["status", "expires_at"], name: "index_family_invitations_on_status_and_expires_at"
t.index ["status", "updated_at"], name: "index_family_invitations_on_status_and_updated_at"
t.index ["token"], name: "index_family_invitations_on_token", unique: true
end
@ -126,8 +126,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_30_150256) do
t.integer "role", default: 1, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["family_id", "role"], name: "index_family_memberships_on_family_id_and_role"
t.index ["family_id"], name: "index_family_memberships_on_family_id"
t.index ["family_id", "role"], name: "index_family_memberships_on_family_and_role"
t.index ["user_id"], name: "index_family_memberships_on_user_id", unique: true
end
@ -316,6 +315,16 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_30_150256) do
t.integer "status", default: 0
t.datetime "active_until"
t.integer "points_count", default: 0, null: false
t.string "provider"
t.string "uid"
t.text "patreon_access_token"
t.text "patreon_refresh_token"
t.datetime "patreon_token_expires_at"
t.string "utm_source"
t.string "utm_medium"
t.string "utm_campaign"
t.string "utm_term"
t.string "utm_content"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
@ -342,11 +351,11 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_30_150256) do
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "areas", "users"
add_foreign_key "families", "users", column: "creator_id", validate: false
add_foreign_key "family_invitations", "families", validate: false
add_foreign_key "family_invitations", "users", column: "invited_by_id", validate: false
add_foreign_key "family_memberships", "families", validate: false
add_foreign_key "family_memberships", "users", validate: false
add_foreign_key "families", "users", column: "creator_id"
add_foreign_key "family_invitations", "families"
add_foreign_key "family_invitations", "users", column: "invited_by_id"
add_foreign_key "family_memberships", "families"
add_foreign_key "family_memberships", "users"
add_foreign_key "notifications", "users"
add_foreign_key "place_visits", "places"
add_foreign_key "place_visits", "visits"

50
package-lock.json generated
View file

@ -13,7 +13,7 @@
"trix": "^2.1.15"
},
"devDependencies": {
"@playwright/test": "^1.54.1",
"@playwright/test": "^1.56.1",
"@types/node": "^24.0.13"
},
"engines": {
@ -39,13 +39,12 @@
}
},
"node_modules/@playwright/test": {
"version": "1.54.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.1.tgz",
"integrity": "sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==",
"version": "1.56.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz",
"integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.54.1"
"playwright": "1.56.1"
},
"bin": {
"playwright": "cli.js"
@ -169,7 +168,6 @@
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
@ -206,13 +204,12 @@
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"node_modules/playwright": {
"version": "1.54.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.1.tgz",
"integrity": "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==",
"version": "1.56.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz",
"integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.54.1"
"playwright-core": "1.56.1"
},
"bin": {
"playwright": "cli.js"
@ -225,11 +222,10 @@
}
},
"node_modules/playwright-core": {
"version": "1.54.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz",
"integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==",
"version": "1.56.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz",
"integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
@ -328,12 +324,12 @@
}
},
"@playwright/test": {
"version": "1.54.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.1.tgz",
"integrity": "sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==",
"version": "1.56.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz",
"integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==",
"dev": true,
"requires": {
"playwright": "1.54.1"
"playwright": "1.56.1"
}
},
"@rails/actioncable": {
@ -443,19 +439,19 @@
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"playwright": {
"version": "1.54.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.1.tgz",
"integrity": "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==",
"version": "1.56.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz",
"integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==",
"dev": true,
"requires": {
"fsevents": "2.3.2",
"playwright-core": "1.54.1"
"playwright-core": "1.56.1"
}
},
"playwright-core": {
"version": "1.54.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz",
"integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==",
"version": "1.56.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz",
"integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==",
"dev": true
},
"postcss": {

View file

@ -12,7 +12,7 @@
"npm": "9.6.7"
},
"devDependencies": {
"@playwright/test": "^1.54.1",
"@playwright/test": "^1.56.1",
"@types/node": "^24.0.13"
},
"scripts": {}

BIN
screenshots/family.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 546 KiB

BIN
screenshots/map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

BIN
screenshots/stats.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
screenshots/trips.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

View file

@ -325,4 +325,179 @@ RSpec.describe 'Users::Registrations', type: :request do
end
end
end
describe 'UTM Parameter Tracking' do
let(:utm_params) do
{
utm_source: 'google',
utm_medium: 'cpc',
utm_campaign: 'winter_2025',
utm_term: 'location_tracking',
utm_content: 'banner_ad'
}
end
context 'when self-hosted mode is disabled' do
before do
allow(DawarichSettings).to receive(:self_hosted?).and_return(false)
end
it 'captures UTM parameters from registration page URL' do
get new_user_registration_path, params: utm_params
expect(response).to have_http_status(:ok)
expect(session[:utm_source]).to eq('google')
expect(session[:utm_medium]).to eq('cpc')
expect(session[:utm_campaign]).to eq('winter_2025')
expect(session[:utm_term]).to eq('location_tracking')
expect(session[:utm_content]).to eq('banner_ad')
end
it 'stores UTM parameters in user record after registration' do
# Visit registration page with UTM params
get new_user_registration_path, params: utm_params
# Create account
unique_email = "utm-user-#{Time.current.to_i}@example.com"
post user_registration_path, params: {
user: {
email: unique_email,
password: 'password123',
password_confirmation: 'password123'
}
}
# Verify UTM params were saved to user
user = User.find_by(email: unique_email)
expect(user.utm_source).to eq('google')
expect(user.utm_medium).to eq('cpc')
expect(user.utm_campaign).to eq('winter_2025')
expect(user.utm_term).to eq('location_tracking')
expect(user.utm_content).to eq('banner_ad')
end
it 'clears UTM parameters from session after registration' do
# Visit registration page with UTM params
get new_user_registration_path, params: utm_params
# Create account
unique_email = "utm-cleanup-#{Time.current.to_i}@example.com"
post user_registration_path, params: {
user: {
email: unique_email,
password: 'password123',
password_confirmation: 'password123'
}
}
# Verify session was cleaned up
expect(session[:utm_source]).to be_nil
expect(session[:utm_medium]).to be_nil
expect(session[:utm_campaign]).to be_nil
expect(session[:utm_term]).to be_nil
expect(session[:utm_content]).to be_nil
end
it 'handles partial UTM parameters' do
partial_utm = { utm_source: 'twitter', utm_campaign: 'spring_promo' }
get new_user_registration_path, params: partial_utm
unique_email = "partial-utm-#{Time.current.to_i}@example.com"
post user_registration_path, params: {
user: {
email: unique_email,
password: 'password123',
password_confirmation: 'password123'
}
}
user = User.find_by(email: unique_email)
expect(user.utm_source).to eq('twitter')
expect(user.utm_campaign).to eq('spring_promo')
expect(user.utm_medium).to be_nil
expect(user.utm_term).to be_nil
expect(user.utm_content).to be_nil
end
it 'does not store empty UTM parameters' do
empty_utm = {
utm_source: '',
utm_medium: '',
utm_campaign: 'campaign_only'
}
get new_user_registration_path, params: empty_utm
unique_email = "empty-utm-#{Time.current.to_i}@example.com"
post user_registration_path, params: {
user: {
email: unique_email,
password: 'password123',
password_confirmation: 'password123'
}
}
user = User.find_by(email: unique_email)
expect(user.utm_source).to be_nil
expect(user.utm_medium).to be_nil
expect(user.utm_campaign).to eq('campaign_only')
end
it 'works with family invitations' do
get new_user_registration_path, params: utm_params.merge(invitation_token: invitation.token)
post user_registration_path, params: {
user: {
email: invitation.email,
password: 'password123',
password_confirmation: 'password123'
},
invitation_token: invitation.token
}
user = User.find_by(email: invitation.email)
expect(user.utm_source).to eq('google')
expect(user.utm_campaign).to eq('winter_2025')
expect(user.family).to eq(family)
end
end
context 'when self-hosted mode is enabled' do
before do
allow(ENV).to receive(:[]).and_call_original
allow(ENV).to receive(:[]).with('SELF_HOSTED').and_return('true')
end
it 'does not capture UTM parameters' do
# With valid invitation to allow registration in self-hosted mode
get new_user_registration_path, params: utm_params.merge(invitation_token: invitation.token)
expect(session[:utm_source]).to be_nil
expect(session[:utm_medium]).to be_nil
expect(session[:utm_campaign]).to be_nil
end
it 'does not store UTM parameters in user record' do
# With valid invitation to allow registration in self-hosted mode
get new_user_registration_path, params: utm_params.merge(invitation_token: invitation.token)
post user_registration_path, params: {
user: {
email: invitation.email,
password: 'password123',
password_confirmation: 'password123'
},
invitation_token: invitation.token
}
user = User.find_by(email: invitation.email)
expect(user.utm_source).to be_nil
expect(user.utm_medium).to be_nil
expect(user.utm_campaign).to be_nil
expect(user.utm_term).to be_nil
expect(user.utm_content).to be_nil
end
end
end
end