diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f721b8c..eacd7c1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,13 @@ 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.30.9] - 2025-08-11 +# [0.30.9] - 2025-08-13 ## Added +- QR code for API key is implemented but hidden under feature flag until the iOS app supports it. - X-Dawarich-Response and X-Dawarich-Version headers are now returned for all API responses. +- Trial version for cloud users is now available. # [0.30.8] - 2025-08-01 diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 4bff870e..710f9b60 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -59,11 +59,11 @@ class StatsController < ApplicationController @stats.each do |year, stats| stats_by_month = stats.index_by(&:month) - + year_distances[year] = (1..12).map do |month| month_name = Date::MONTHNAMES[month] distance = stats_by_month[month]&.distance || 0 - + [month_name, distance] end end diff --git a/app/javascript/controllers/direct_upload_controller.js b/app/javascript/controllers/direct_upload_controller.js index 5be5b921..cc58436e 100644 --- a/app/javascript/controllers/direct_upload_controller.js +++ b/app/javascript/controllers/direct_upload_controller.js @@ -5,7 +5,8 @@ import { showFlashMessage } from "../maps/helpers" export default class extends Controller { static targets = ["input", "progress", "progressBar", "submit", "form"] static values = { - url: String + url: String, + userTrial: Boolean } connect() { @@ -50,6 +51,22 @@ export default class extends Controller { const files = this.inputTarget.files if (files.length === 0) return + // Check file size limits for trial users + if (this.userTrialValue) { + const MAX_FILE_SIZE = 11 * 1024 * 1024 // 11MB in bytes + const oversizedFiles = Array.from(files).filter(file => file.size > MAX_FILE_SIZE) + + if (oversizedFiles.length > 0) { + const fileNames = oversizedFiles.map(f => f.name).join(', ') + const message = `File size limit exceeded. Trial users can only upload files up to 10MB. Oversized files: ${fileNames}` + showFlashMessage('error', message) + + // Clear the file input + this.inputTarget.value = '' + return + } + } + console.log(`Uploading ${files.length} files`) this.isUploading = true diff --git a/app/javascript/controllers/onboarding_modal_controller.js b/app/javascript/controllers/onboarding_modal_controller.js new file mode 100644 index 00000000..5a20e1c2 --- /dev/null +++ b/app/javascript/controllers/onboarding_modal_controller.js @@ -0,0 +1,42 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["modal"] + static values = { showable: Boolean } + + connect() { + if (this.showableValue) { + // Listen for Turbo page load events to show modal after navigation completes + document.addEventListener('turbo:load', this.handleTurboLoad.bind(this)) + } + } + + disconnect() { + // Clean up event listener when controller is removed + document.removeEventListener('turbo:load', this.handleTurboLoad.bind(this)) + } + + handleTurboLoad() { + if (this.showableValue) { + this.checkAndShowModal() + } + } + + checkAndShowModal() { + const MODAL_STORAGE_KEY = 'dawarich_onboarding_shown' + const hasShownModal = localStorage.getItem(MODAL_STORAGE_KEY) + + if (!hasShownModal && this.hasModalTarget) { + // Show the modal + this.modalTarget.showModal() + + // Mark as shown in local storage + localStorage.setItem(MODAL_STORAGE_KEY, 'true') + + // Add event listener to handle when modal is closed + this.modalTarget.addEventListener('close', () => { + // Modal closed - state already saved + }) + } + } +} diff --git a/app/jobs/users/trial_webhook_job.rb b/app/jobs/users/trial_webhook_job.rb index d908d8c5..6e29b462 100644 --- a/app/jobs/users/trial_webhook_job.rb +++ b/app/jobs/users/trial_webhook_job.rb @@ -6,10 +6,14 @@ class Users::TrialWebhookJob < ApplicationJob def perform(user_id) user = User.find(user_id) - token = Subscription::EncodeJwtToken.new( - { user_id: user.id, email: user.email, action: 'create_user' }, - ENV['JWT_SECRET_KEY'] - ).call + payload = { + user_id: user.id, + email: user.email, + active_until: user.active_until, + action: 'create_user' + } + + token = Subscription::EncodeJwtToken.new(payload, ENV['JWT_SECRET_KEY']).call request_url = "#{ENV['MANAGER_URL']}/api/v1/users" headers = { diff --git a/app/models/import.rb b/app/models/import.rb index d22d5174..08f1cfa5 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -13,6 +13,7 @@ class Import < ApplicationRecord after_commit :remove_attached_file, on: :destroy validates :name, presence: true, uniqueness: { scope: :user_id } + validate :file_size_within_limit, if: -> { user.trial? } enum :status, { created: 0, processing: 1, completed: 2, failed: 3 } @@ -22,6 +23,18 @@ class Import < ApplicationRecord user_data_archive: 8 } + private + + def file_size_within_limit + return unless file.attached? + + if file.blob.byte_size > 11.megabytes + errors.add(:file, 'is too large. Trial users can only upload files up to 10MB.') + end + end + + public + def process! if user_data_archive? process_user_data_archive! diff --git a/app/models/user.rb b/app/models/user.rb index 36b1633f..db156935 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -98,7 +98,7 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength end def can_subscribe? - (active_until.nil? || active_until&.past?) && !DawarichSettings.self_hosted? + (trial? || !active_until&.future?) && !DawarichSettings.self_hosted? end def generate_subscription_token @@ -130,7 +130,6 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength end def activate - # TODO: Remove the `status` column in the future. update(status: :active, active_until: 1000.years.from_now) end diff --git a/app/policies/import_policy.rb b/app/policies/import_policy.rb index 0d1ceddf..fcaa2347 100644 --- a/app/policies/import_policy.rb +++ b/app/policies/import_policy.rb @@ -11,13 +11,13 @@ class ImportPolicy < ApplicationPolicy user.present? && record.user == user end - # Users can create new imports if they are active + # Users can create new imports if they are active or trial def new? create? end def create? - user.present? && user.active? + user.present? && (user.active? || user.trial?) end # Users can only edit their own imports diff --git a/app/services/imports/create.rb b/app/services/imports/create.rb index d86fe337..d7ad2323 100644 --- a/app/services/imports/create.rb +++ b/app/services/imports/create.rb @@ -56,7 +56,12 @@ class Imports::Create end def schedule_visit_suggesting(user_id, import) + return unless user.safe_settings.visits_suggestions_enabled? + points = import.points.order(:timestamp) + + return if points.none? + start_at = Time.zone.at(points.first.timestamp) end_at = Time.zone.at(points.last.timestamp) diff --git a/app/views/devise/registrations/_api_key.html.erb b/app/views/devise/registrations/_api_key.html.erb index a1230a2e..aeba5bfd 100644 --- a/app/views/devise/registrations/_api_key.html.erb +++ b/app/views/devise/registrations/_api_key.html.erb @@ -2,12 +2,12 @@
Use this API key to authenticate your requests.
<%= current_user.api_key %>
- <%# if ENV['QR_CODE_ENABLED'] == 'true' %>
+ <% if ENV['QR_CODE_ENABLED'] == 'true' %>
Or you can scan it in your Dawarich iOS app: <%= api_key_qr_code(current_user) %>
- <%# end %> + <% end %>
Docs: <%= link_to "API documentation", '/api-docs', class: 'underline hover:no-underline' %>
diff --git a/app/views/imports/_form.html.erb b/app/views/imports/_form.html.erb index 35d2ec34..3f2857fb 100644 --- a/app/views/imports/_form.html.erb +++ b/app/views/imports/_form.html.erb @@ -1,6 +1,7 @@ <%= form_with model: import, class: "contents", data: { controller: "direct-upload", direct_upload_url_value: rails_direct_uploads_url, + direct_upload_user_trial_value: current_user.trial?, direct_upload_target: "form" } do |form| %>