Skip to content

Commit

Permalink
feat: added terms and conditions page solves #61 with advanced onboar…
Browse files Browse the repository at this point in the history
…ding solves #62
  • Loading branch information
kjellberg committed Sep 9, 2024
1 parent a80fcb9 commit a8e3ae5
Show file tree
Hide file tree
Showing 21 changed files with 305 additions and 42 deletions.
23 changes: 23 additions & 0 deletions .solargraph.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
include:
- "**/*.rb"
exclude:
- spec/**/*
- test/**/*
- vendor/**/*
- ".bundle/**/*"
require: []
domains: []
reporters:
- rubocop
- require_not_found
formatter:
rubocop:
cops: safe
except: []
only: []
extra_args: []
require_paths: []
plugins:
- solargraph-rails
max_files: 5000
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ group :rubocop do
gem "rubocop", ">= 1.25.1", require: false
gem "rubocop-rails-omakase", require: false
end

gem "solargraph", "~> 0.50.0"
gem "solargraph-rails", "~> 1.1"
58 changes: 43 additions & 15 deletions app/controllers/kiqr/onboarding_controller.rb
Original file line number Diff line number Diff line change
@@ -1,42 +1,70 @@
class Kiqr::OnboardingController < KiqrController
include Wicked::Wizard

steps *::User::ONBOARDING_STEPS

skip_before_action :ensure_onboarded

before_action :setup_user

before_action do
# This is to prevent users from accessing the onboarding process
# if they have already completed it. A user can only have one
# personal account. If they have one, they have completed the
# onboarding process.
# if they have already completed it.
redirect_to dashboard_path if current_user.onboarded?

# This is to set the breadcrumbs for the onboarding process.
add_breadcrumb I18n.t("kiqr.breadcrumbs.onboarding"), onboarding_path
# Set the breadcrumbs for the onboarding process.
add_breadcrumb I18n.t("kiqr.breadcrumbs.onboarding"), onboarding_path(current_user.onboarding_step)
end

def show
@user.build_personal_account(personal: true)

render_wizard
end

def update
@user.assign_attributes(user_params)
@user.personal_account.personal = true

if @user.save
kiqr_flash_message(:notice, :onboarding_completed)
redirect_to after_onboarding_path(@user)
else
render :show, status: :unprocessable_entity
case step
when :terms_and_conditions
unless @user.update(terms_and_conditions_params)
return render json: @user
kiqr_flash_message(:alert, :accept_terms_and_conditions_required)
end
when :profile
@user.build_personal_account(personal: true)
@user.update(profile_params)
end

render_wizard @user
end

private

def finish_wizard_path
after_onboarding_path(@user)
end

def setup_user
@user = User.find(current_user.id)
end

def user_params
def terms_and_conditions_params
onboarding_params(:terms_accepted)
end

def profile_params
account_attributes = Kiqr.config.account_attributes.prepend(:id)
params.require(:user).permit(:time_zone, :locale, personal_account_attributes: account_attributes)
onboarding_params(:email, :time_zone, :locale, personal_account_attributes: account_attributes)
end

protected

def onboarding_params(*permitted_attributes)
next_step = @user.next_onboarding_step

if next_step
params.require(:user).permit(*permitted_attributes).merge({ onboarding_step: @user.next_onboarding_step })
else
params.require(:user).permit(*permitted_attributes).merge({ onboarding_step: "onboarded", onboarded_at: Time.zone.now })
end
end
end
2 changes: 1 addition & 1 deletion app/controllers/kiqr/users/invitations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ def ensure_onboarded

session[:after_sign_in_path] = user_invitation_path(token: params[:token], account_id: nil)
kiqr_flash_message(:notice, :onboard_to_accept_invitation)
redirect_to onboarding_path
redirect_to onboarding_path(current_user.onboarding_step)
end
end
2 changes: 1 addition & 1 deletion app/controllers/kiqr_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def kiqr_flash_message_now(type, message, **kwargs)

# Redirect path after sign-in. If the user hasn't completed onboarding, redirect to onboarding.
def after_sign_in_path_for(resource)
return onboarding_path unless resource.onboarded?
return onboarding_path(resource.onboarding_step) unless resource.onboarded?

session.delete(:after_sign_in_path) || dashboard_path
end
Expand Down
40 changes: 40 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,43 @@
class User < ApplicationRecord
kiqr model: :user

ONBOARDING_STEPS = %i[terms_and_conditions profile]

validates_presence_of :terms_accepted_at, if: -> { onboarded_or_completed_step?(:terms_and_conditions) }
validates_presence_of :personal_account, if: -> { onboarded_or_completed_step?(:profile) }

after_initialize :setup_onboarding_attributes

attr_accessor :terms_accepted

def terms_accepted=(value)
accepted = ActiveRecord::Type::Boolean.new.cast(value)
self.terms_accepted_at = Time.zone.now if accepted
end

def next_onboarding_step
return nil if ONBOARDING_STEPS.index(self.onboarding_step.to_sym) == ONBOARDING_STEPS.size - 1

ONBOARDING_STEPS[ONBOARDING_STEPS.index(self.onboarding_step.to_sym) + 1]
end

# Check if the user has been onboarded successfully.
# The onboarded at timestamp will be set after a user has
# completed the onboarding wizard.
def onboarded?
onboarded_at.present?
end

protected

def setup_onboarding_attributes
self.onboarding_step = ONBOARDING_STEPS.first unless self.onboarding_step.present?
end

# If the user has completed the onboarding process, return true.
# Otherwise, check if the user has completed the specified step.
def onboarded_or_completed_step?(step)
onboarding_step_index = ONBOARDING_STEPS.index(self.onboarding_step)
onboarded? || (onboarding_step_index && onboarding_step_index > ONBOARDING_STEPS.index(step))
end
end
11 changes: 8 additions & 3 deletions config/locales/kiqr.en.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
en:
flash_messages:
accept_terms_and_conditions_required: You must accept the terms and conditions to continue.
accepted_invitation: Invitation accepted successfully.
account_created: Team created successfully.
account_deleted: Team deleted successfully.
Expand Down Expand Up @@ -130,10 +131,14 @@ en:
These steps ensure that all team data is properly managed before deletion. If you need assistance with removing members, transferring ownership, or have any questions, our support team is here to help. Thank you for your cooperation in maintaining data security and integrity.
onboarding:
show:
description: Finish the registration by setting up your user profile.
terms_and_conditions:
title: Terms & Conditions
description: Please read through and accept our terms & conditions to continue the onboarding.
submit: Accept and continue
profile:
title: User profile
description: Finish the registration by setting up your user profile and personal perferences.
submit: Finish onboarding
title: Setup your personal account
registrations:
delete:
confirmation_message: Are you sure? This will DELETE all of your data.
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20240321094123_devise_create_users.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,14 @@ def change
t.boolean :otp_required_for_login, default: false
t.text :otp_backup_codes

## Other user attributes
t.string :locale, default: "en"
t.string :time_zone, default: "UTC"

t.datetime :onboarded_at
t.string :onboarding_step
t.datetime :terms_accepted_at

t.timestamps null: false
end

Expand Down
1 change: 1 addition & 0 deletions kiqr.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Gem::Specification.new do |s|
s.add_dependency "omniauth", "~> 2.1.1"
s.add_dependency "omniauth-rails_csrf_protection", "~> 1.0.1"
s.add_dependency "public_uid", "~> 2.2"
s.add_dependency "wicked", "~> 2.0"

# Frontend dependencies
s.add_dependency "meta-tags", "~> 2.21"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<h2>1. Introduction</h2>
<p>This Privacy Policy describes how <a href="%22we%22," title="us&quot;, &quot;our">App Name</a> collects, uses, and shares information about you when you use our SaaS application ("Service"). By using the Service, you agree to the collection and use of information in accordance with this Privacy Policy.</p>

<h2>2. Information We Collect</h2>

<h3>a. Information You Provide</h3>
<p>We may collect the following personal information that you provide to us:</p>

<ul>
<li>Name</li>
<li>Email address</li>
<li>Billing and payment information</li>
<li>Account credentials</li>
</ul>

<h3>b. Information Collected Automatically</h3>
<p>We may automatically collect information about your use of the Service, including:</p>

<ul>
<li>IP address</li>
<li>Device information (such as browser type, operating system)</li>
<li>Usage data (e.g., pages viewed, time spent on the Service)</li>
</ul>

<h3>c. Cookies and Tracking Technologies</h3>
<p>We use cookies and similar tracking technologies to enhance your experience and collect information about how you use the Service. You can control cookies through your browser settings.</p>

<h2>3. How We Use Your Information</h2>
<p>We may use your information for the following purposes:</p>

<ul>
<li>To provide, operate, and maintain the Service</li>
<li>To process payments and manage billing</li>
<li>To communicate with you regarding your account or the Service</li>
<li>To improve the Service and develop new features</li>
<li>To comply with legal obligations</li>
</ul>

<h2>4. Sharing Your Information</h2>
<p>We will not share your personal information with third parties, except in the following circumstances:</p>

<ul>
<li><strong>Service Providers:</strong> We may share information with third-party service providers who assist us in operating the Service (e.g., payment processors, hosting providers).</li>
<li><strong>Legal Compliance:</strong> We may disclose your information if required to do so by law or if we believe such action is necessary to comply with legal obligations.</li>
</ul>

<h2>5. Data Retention</h2>
<p>We will retain your personal information for as long as necessary to fulfill the purposes described in this Privacy Policy, unless a longer retention period is required or permitted by law.</p>

<h2>6. Security of Your Information</h2>
<p>We take reasonable steps to protect your personal information from unauthorized access, use, or disclosure. However, no method of transmission over the internet or electronic storage is 100% secure.</p>

<h2>7. Your Data Protection Rights</h2>
<p>You have the following rights regarding your personal data:</p>

<ul>
<li><strong>Access:</strong> You can request a copy of the personal data we hold about you.</li>
<li><strong>Rectification:</strong> You can request that we correct any inaccurate or incomplete information.</li>
<li><strong>Erasure:</strong> You can request that we delete your personal data, subject to certain legal obligations.</li>
<li><strong>Data Portability:</strong> You can request a copy of your data in a machine-readable format.</li>
</ul>

<p>To exercise these rights, please contact us at [Support Email Address].</p>

<h2>8. International Data Transfers</h2>
<p>Your information may be transferred to, and maintained on, servers located outside of your country. By using the Service, you consent to such data transfers.</p>

<h2>9. Changes to This Privacy Policy</h2>
<p>We may update this Privacy Policy from time to time. We will notify you of any significant changes by posting the new Privacy Policy on the Service. Your continued use of the Service after changes are posted constitutes your acceptance of the revised Privacy Policy.</p>

<h2>10. Contact Us</h2>
<p>If you have any questions or concerns about this Privacy Policy or our data practices, please contact us at [Support Email Address].</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<h2>1. Acceptance of Terms</h2>
<p>By using or accessing our [App Name] SaaS application ("Service"), you agree to be bound by these Terms and Conditions ("Terms"). If you do not agree with these Terms, do not use the Service.</p>

<h2>2. Eligibility</h2>
<p>You must be at least 18 years old to use the Service. By agreeing to these Terms, you represent and warrant that you have the legal capacity to enter into a binding agreement.</p>

<h2>3. Account Registration</h2>
<p>To access certain features of the Service, you must register for an account. You are responsible for keeping your account information confidential and for all activities that occur under your account. You must notify us immediately if you suspect any unauthorized use of your account.</p>

<h2>4. Subscription and Payment</h2>
<p>The Service is offered on a subscription basis. By subscribing, you agree to pay the applicable fees as described in your plan. We reserve the right to change the subscription fees at any time with prior notice. All payments are non-refundable unless explicitly stated.</p>

<h2>5. Free Trial</h2>
<p>We may offer a free trial period. If you do not cancel before the trial ends, your subscription will automatically renew at the regular price.</p>

<h2>6. Use of the Service</h2>
<p>You agree to use the Service only for lawful purposes and in compliance with all applicable laws. You may not:</p>

<ul>
<li>Modify, copy, or distribute any part of the Service without permission.</li>
<li>Use the Service for any illegal or unauthorized purpose.</li>
<li>Attempt to disrupt or compromise the Service.</li>
</ul>

<h2>7. Data and Privacy</h2>
<p>By using the Service, you agree to our <a href="#">Privacy Policy</a> regarding the collection, use, and storage of your personal data. You retain all ownership rights to your data, but grant us a license to use it to provide the Service.</p>

<h2>8. Intellectual Property</h2>
<p>All content, trademarks, and materials on the Service are the property of [Your Company] or its licensors. You may not reproduce, distribute, or create derivative works without our prior written consent.</p>

<h2>9. Termination</h2>
<p>We reserve the right to terminate or suspend your account at our sole discretion if you violate these Terms or engage in any prohibited activities. Upon termination, your access to the Service will be immediately revoked, and any outstanding fees will become due.</p>

<h2>10. Limitation of Liability</h2>
<p>To the fullest extent permitted by law, [Your Company] shall not be liable for any indirect, incidental, or consequential damages arising out of or related to your use or inability to use the Service.</p>

<h2>11. Indemnification</h2>
<p>You agree to indemnify and hold [Your Company] harmless from any claims, damages, liabilities, and expenses (including legal fees) arising from your use of the Service or violation of these Terms.</p>

<h2>12. Modifications</h2>
<p>We reserve the right to modify these Terms at any time. Changes will be effective when posted. Continued use of the Service constitutes your acceptance of the updated Terms.</p>

<h2>13. Governing Law</h2>
<p>These Terms shall be governed by and construed in accordance with the laws of [Your Country/State]. Any disputes arising under these Terms will be resolved in the courts located in [Your Jurisdiction].</p>

<h2>14. Contact Information</h2>
<p>If you have any questions regarding these Terms, please contact us at [Support Email Address].</p>
1 change: 1 addition & 0 deletions lib/kiqr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
require "omniauth"
require "omniauth/rails_csrf_protection"
require "public_uid"
require "wicked"

# Frontend dependencies
require "irelia"
Expand Down
2 changes: 1 addition & 1 deletion lib/kiqr/controllers/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def personal_account
def ensure_onboarded
return if devise_controller? # Skip onboarding check for devise controllers

redirect_to onboarding_path if user_signed_in? && !current_user.onboarded?
redirect_to onboarding_path(current_user.onboarding_step) if user_signed_in? && !current_user.onboarded?
end

# Ensure that the user has selected a team account before proceeding.
Expand Down
5 changes: 0 additions & 5 deletions lib/kiqr/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,6 @@ def otp_uri
otp_provisioning_uri(email, issuer: issuer)
end

# Check if the user has been onboarded successfully.
def onboarded?
personal_account&.persisted?
end

# Alternative to the devise update_with_password method to
# allow users to add a password without providing their current password.
def create_password(params)
Expand Down
2 changes: 1 addition & 1 deletion lib/kiqr/rails/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def kiqr_routes(options = {})
kiqr_invitation_routes(options)
kiqr_user_routes(options)

resource :onboarding, only: [ :show, :update ], controller: options[:controllers][:onboarding]
resources :onboarding, only: [ :show, :update ], controller: options[:controllers][:onboarding]
end

protected
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<%= irelia_page_header(title: t(".title"), subline: t(".description")) %>
<%= irelia_page_header title: t(".title"), subline: t(".description") %>

<%= irelia_container size: :md do %>
<%= irelia_form(model: @user, url: onboarding_path) do |form| %>
<%= irelia_form(model: @user, url: wizard_path) do |form| %>
<!-- Form fields for the account object -->
<%= form.fields_for :personal_account, @user.personal_account do |personal_account_form| %>
<%= render "kiqr/accounts/form_fields", form: personal_account_form %>
Expand Down
Loading

0 comments on commit a8e3ae5

Please sign in to comment.