|
|
@ -1 +1 @@
|
|||
0.34.0
|
||||
0.34.1
|
||||
|
|
|
|||
14
CHANGELOG.md
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
|||
205
Gemfile.lock
|
|
@ -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
|
||||
|
||||
|
|
|
|||
20
README.md
|
|
@ -8,14 +8,17 @@
|
|||
|
||||
## 📸 Screenshots
|
||||
|
||||

|
||||

|
||||
*Map View*
|
||||
|
||||

|
||||

|
||||
*Family Page*
|
||||
|
||||

|
||||
*Statistics Overview*
|
||||
|
||||

|
||||
*Imports page*
|
||||

|
||||
*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
|
||||
|
|
|
|||
33
app/controllers/concerns/utm_trackable.rb
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
@ -65,8 +69,8 @@ class Users::RegistrationsController < Devise::RegistrationsController
|
|||
|
||||
def invitation_token
|
||||
@invitation_token ||= params[:invitation_token] ||
|
||||
params.dig(:user, :invitation_token) ||
|
||||
session[:invitation_token]
|
||||
params.dig(:user, :invitation_token) ||
|
||||
session[:invitation_token]
|
||||
end
|
||||
|
||||
def accept_invitation_for_user(user)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>, © <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>, © <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>, © <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>, © <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>, © <a href='https://openstreetmap.org'>OpenStreetMap</a>"
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
@ -44,4 +46,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
@ -71,29 +70,30 @@
|
|||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Full Screen Map -->
|
||||
<div
|
||||
id='map'
|
||||
class="absolute inset-0 w-full h-full z-0"
|
||||
data-controller="maps points add-visit family-members"
|
||||
data-points-target="map"
|
||||
data-api_key="<%= current_user.api_key %>"
|
||||
data-self_hosted="<%= @self_hosted %>"
|
||||
data-user_settings='<%= current_user.safe_settings.settings.to_json %>'
|
||||
data-user_theme="<%= current_user&.theme || 'dark' %>"
|
||||
data-coordinates='<%= @coordinates.to_json.html_safe %>'
|
||||
data-tracks='<%= @tracks.to_json.html_safe %>'
|
||||
data-distance="<%= @distance %>"
|
||||
data-points_number="<%= @points_number %>"
|
||||
data-timezone="<%= Rails.configuration.time_zone %>"
|
||||
data-features='<%= @features.to_json.html_safe %>'
|
||||
data-family-members-features-value='<%= @features.to_json.html_safe %>'
|
||||
data-family-members-user-theme-value="<%= current_user&.theme || 'dark' %>">
|
||||
<div data-maps-target="container" class="w-full h-full">
|
||||
<div id="fog" class="fog"></div>
|
||||
<!-- Map Container - Fills remaining space -->
|
||||
<div class="w-full h-full">
|
||||
<div
|
||||
id='map'
|
||||
class="w-full h-full"
|
||||
data-controller="maps points add-visit family-members"
|
||||
data-points-target="map"
|
||||
data-api_key="<%= current_user.api_key %>"
|
||||
data-self_hosted="<%= @self_hosted %>"
|
||||
data-user_settings='<%= current_user.safe_settings.settings.to_json %>'
|
||||
data-user_theme="<%= current_user&.theme || 'dark' %>"
|
||||
data-coordinates='<%= @coordinates.to_json.html_safe %>'
|
||||
data-tracks='<%= @tracks.to_json.html_safe %>'
|
||||
data-distance="<%= @distance %>"
|
||||
data-points_number="<%= @points_number %>"
|
||||
data-timezone="<%= Rails.configuration.time_zone %>"
|
||||
data-features='<%= @features.to_json.html_safe %>'
|
||||
data-family-members-features-value='<%= @features.to_json.html_safe %>'
|
||||
data-family-members-user-theme-value="<%= current_user&.theme || 'dark' %>">
|
||||
<div data-maps-target="container" class="w-full h-full">
|
||||
<div id="fog" class="fog"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -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'>
|
||||
|
|
|
|||
11
db/migrate/20251030190924_add_utm_parameters_to_users.rb
Normal 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
|
|
@ -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
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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
|
After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 546 KiB |
BIN
screenshots/map.png
Normal file
|
After Width: | Height: | Size: 6.9 MiB |
|
Before Width: | Height: | Size: 134 KiB |
BIN
screenshots/stats.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
screenshots/trips.png
Normal file
|
After Width: | Height: | Size: 434 KiB |
|
|
@ -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
|
||||
|
|
|
|||