diff --git a/app/controllers/app/onboarding_progress_controller.rb b/app/controllers/app/onboarding_progress_controller.rb new file mode 100644 index 0000000..ce817a1 --- /dev/null +++ b/app/controllers/app/onboarding_progress_controller.rb @@ -0,0 +1,69 @@ +class App::OnboardingProgressController < ApplicationController + skip_authorization_check + + ALLOWED_OPERATIONS = %w[complete_step dismiss resume finish].freeze + + def show + render json: response_payload + end + + def update + operation = params[:operation].to_s + + return render_invalid_operation if operation.blank? || !ALLOWED_OPERATIONS.include?(operation) + + case operation + when "complete_step" + return render_missing_step_key if params[:step_key].blank? + + onboarding_progress.complete_step!(params[:step_key]) + when "dismiss" + onboarding_progress.dismiss! + when "resume" + onboarding_progress.resume! + when "finish" + onboarding_progress.finish! + end + + render json: response_payload + rescue ArgumentError => e + render json: { success: false, error: e.message }, status: :unprocessable_entity + rescue ActiveRecord::RecordInvalid => e + render json: { success: false, error: e.record.errors.full_messages.to_sentence }, status: :unprocessable_entity + end + + private + + def onboarding_progress + @onboarding_progress ||= UserOnboardingProgress.find_or_create_by!(user: current_user) + end + + def response_payload + { + success: true, + onboarding_progress: { + completed_steps: onboarding_progress.completed_steps, + completed_steps_count: onboarding_progress.completed_steps_count, + steps_total: UserOnboardingProgress::STEP_KEYS.size, + progress_percentage: onboarding_progress.progress_percentage, + last_seen_step: onboarding_progress.last_seen_step, + dismissed: onboarding_progress.dismissed_at.present?, + dismissed_at: onboarding_progress.dismissed_at, + finished: onboarding_progress.finished_at.present?, + finished_at: onboarding_progress.finished_at, + step_keys: UserOnboardingProgress::STEP_KEYS + } + } + end + + def render_invalid_operation + render json: { + success: false, + error: "Invalid operation. Allowed operations: #{ALLOWED_OPERATIONS.join(', ')}" + }, status: :unprocessable_entity + end + + def render_missing_step_key + render json: { success: false, error: "step_key is required for operation complete_step" }, status: :unprocessable_entity + end +end diff --git a/config/routes/app.rb b/config/routes/app.rb index 269424e..5d5ec33 100644 --- a/config/routes/app.rb +++ b/config/routes/app.rb @@ -2,6 +2,7 @@ root to: "app/dashboard#index", as: :app_dashboard resource :terms_of_use, only: [:show, :update], controller: "app/terms_of_use", as: :app_terms_of_use + resource :onboarding_progress, only: [:show, :update], controller: "app/onboarding_progress", as: :app_onboarding_progress resources :clients, module: "app", as: :app_clients resources :budgets, only: [:index, :new, :create, :show, :edit, :update], module: "app", as: :app_budgets do diff --git a/db/schema.rb b/db/schema.rb index 8b98723..fbeefcc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2026_04_12_110000) do +ActiveRecord::Schema[7.1].define(version: 2026_04_16_113000) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -424,6 +424,18 @@ t.index ["user_id"], name: "index_support_tickets_on_user_id" end + create_table "user_onboarding_progresses", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "user_id", null: false + t.jsonb "completed_steps", default: {}, null: false + t.string "last_seen_step" + t.datetime "dismissed_at" + t.datetime "finished_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["finished_at"], name: "index_user_onboarding_progresses_on_finished_at" + t.index ["user_id"], name: "index_user_onboarding_progresses_on_user_id", unique: true + end + create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false @@ -496,5 +508,6 @@ add_foreign_key "support_tickets", "order_services" add_foreign_key "support_tickets", "users" add_foreign_key "support_tickets", "users", column: "assigned_to_id" + add_foreign_key "user_onboarding_progresses", "users" add_foreign_key "users", "companies" end