Devise

Please review the general guide on how to integrate Devise into Matestack within the docs of matestack-ui-vuejs --> https://docs.matestack.io/matestack-ui-vuejs/integrations/devise

Below you find examples implementing typical Devise flows with matestack-ui-bootstrap

Devise Routes

app/config/routes.rb

Rails.application.routes.draw do
  devise_for :users, controllers: {
    sessions: 'users/sessions',
    registrations: "users/registrations",
    passwords: 'users/passwords'
  }
  # ...
  
end

Devise Controllers

app/controllers/users/sessions_controller

class Users::SessionsController < Devise::SessionsController

  respond_to :html, :json

  # override in order to render a page
  def new
    render Devise::Pages::SignIn, matestack_layout: Devise::Layout
  end

end

app/controllers/users/registrations_controller

class Users::RegistrationsController < Devise::RegistrationsController

  respond_to :html, :json

  # override in order to render a page
  def new
    render Devise::Pages::Registration, matestack_layout: Devise::Layout
  end

end

app/controllers/users/passwords_controller.rb

class Users::PasswordsController < Devise::PasswordsController

  respond_to :html, :json

  # override in order to render a page
  def new
    render Devise::Pages::Passwords::Forgot, matestack_layout: Devise::Layout
  end

  # optional: override default create action, reason below
  def create
    self.resource = resource_class.send_reset_password_instructions(resource_params)
    yield resource if block_given?

    if successfully_sent?(resource)
      respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name))
    else
      # respond_with(resource)
      # override above seen default behavior as this would trigger an error when user is not found by email in DB
      # this indirectly is exposing the info, which emails DO exist in DB by try and error which is not desired IMO
      # just respond as if everything is fine even if the email is not found in DB or something
      respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name))
    end
  end

  # override in order to render a page
  def edit
    self.resource = resource_class.new
    set_minimum_password_length
    resource.reset_password_token = params[:reset_password_token]

    render Devise::Pages::Passwords::Edit, matestack_layout: Devise::Layout
  end

end

Devise Pages Layout

app/matestack/devise/layout.rb

class Devise::Layout < Matestack::Ui::Layout

  def response
    matestack_vue_js_app do
      page_switch do
        yield
      end
    end
  end

end

Login Page

app/matestack/devise/pages/sign_in.rb

class Devise::Pages::SignIn < Matestack::Ui::Page

  def response
    bs_container class: "mt-5" do
      bs_row class: "mt-5", vertical: :center, horizontal: :center do
        bs_col lg: 4 do
          login_form_partial
        end
      end
    end
  end

  private

  def login_form_partial
    section class: "mt-5 rounded p-4 mb-4" do
      div class: "d-flex justify-content-between" do
        heading size: 2, text: 'Sign In'
        transition path: new_user_registration_path, delay: 300 do
          bs_btn outline: true, variant: :primary, text: "Register"
        end
      end
      matestack_form form_config do
        div class: "mb-3 mt-4" do
          bs_form_input label: 'Email', key: :email, type: :email
        end
        div class: "mb-3" do
          bs_form_input label: 'Password', key: :password, type: :password
        end
        div class: "mb-3" do
          bs_form_submit text: "Sign in"
        end
      end
      toggle show_on: 'sign_in_failure' do
        bs_alert variant: :danger do
          plain 'Your email or password is not valid.'
        end
      end
      hr
      div class: "mt-3" do
        transition path: new_user_password_path, delay: 300 do
          bs_btn outline: true, variant: :secondary, text: "Forgot your password?"
        end
      end
    end
  end

  def form_config
    {
      for: :user, # or whatever you're using
      method: :post,
      path: user_session_path(format: :json), # or whatever you're using
      delay: 500,
      success: {
        redirect: {
          path: your_after_login_path
        }
      },
      failure: {
        emit: 'sign_in_failure'
      }
    }
  end

end

Register Page

app/matestack/devise/pages/registration.rb

class Devise::Pages::Registration < Matestack::Ui::Page

  def response
    bs_container class: "mt-5" do
      bs_row class: "mt-5", vertical: :center, horizontal: :center do
        bs_col lg: 4 do
          register_form_partial
        end
      end
    end
  end

  private

  def register_form_partial
    section class: "mt-5 rounded p-4 mb-4" do
      div class: "d-flex justify-content-between" do
        heading size: 2, text: 'Register'
        transition path: user_session_path, delay: 300 do
          bs_btn outline: true, variant: :primary, text: "Sign in"
        end
      end
      matestack_form form_config do
        div class: "mb-3 mt-4" do
          bs_form_input label: 'Email', key: :email, type: :email
        end
        div class: "mb-3" do
          bs_form_input label: 'Password', key: :password, type: :password
        end
        div class: "mb-3" do
          bs_form_input label: 'Password Confirmation', key: :password_confirmation, type: :password
        end
        div class: "mb-3" do
          bs_form_submit text: "Register"
        end
      end
      toggle show_on: 'register_failure' do
        bs_alert variant: :danger do
          plain 'Registration failed. Do you already have an account?'
        end
      end
    end
  end

  def form_config
    {
      for: :user, # or whatever you're using
      method: :post,
      path: user_registration_path(format: :json), # or whatever you're using
      delay: 500,
      success: {
        redirect: {
          path: your_after_login_path
        }
      },
      failure: {
        emit: 'register_failure'
      }
    }
  end

end

Registration Update

A registration update for a logged in user somewhere within your app could be approached like that:

password_change_config = {
  for: current_user, # or whatever you're using
  method: :put,
  path: user_registration_path(format: :json), # or whatever you're using
  delay: 500,
  success: {
    emit: 'success--user-settings-form',
    reset: true
  },
  failure: {
    emit: 'failure--user-settings-form'
  }
}
    
matestack_form password_change_config do
  div class: "mb-2" do
    bs_form_input key: :email,
      type: :email,
      label: "Email"
  end
  div class: "mb-2" do
    bs_form_input key: :password,
      type: :password,
      label: "New password",
      form_text: "leave blank if you don't want to change the password"
  end
  div class: "mb-2" do
    bs_form_input key: :password_confirmation,
      type: :password,
      label: "New password confirmation",
      form_text: "leave blank if you don't want to change the password"
  end
  div class: "mb-4" do
    bs_form_input key: :current_password,
      type: :password,
      label: "Current password",
      form_text: "we need your current password to confirm your changes"
  end
  div class: "mb-2" do
    bs_form_submit
  end
end

Password Forgotten Page

app/matestack/devise/pages/passwords/forgot.rb

class Devise::Pages::Passwords::Forgot < Matestack::Ui::Page

  def response
    bs_container class: "mt-5" do
      bs_row class: "mt-5", vertical: :center, horizontal: :center do
        bs_col lg: 4 do
          form_partial
        end
      end
    end
  end

  private

    def form_partial
    section class: "mt-5 rounded p-4 mb-4" do
      div class: "d-flex justify-content-between" do
        heading size: 2, text: 'YourAppName - Password'
        transition path: user_session_path, delay: 300 do
          bs_btn outline: true, variant: :primary, text: "Sign in"
        end
      end
      matestack_form form_config do
        div class: "mb-3 mt-4" do
          bs_form_input label: 'Email', key: :email, type: :email
        end
        div class: "mb-3" do
          bs_form_submit text: "Request new password"
        end
      end
      toggle show_on: 'request_password_success' do
        bs_alert variant: :success do
          plain "We've sent you an email with a password reset link."
        end
      end
      toggle show_on: 'request_password_failure', hide_after: 5000 do
        bs_alert variant: :danger do
          plain 'Something went wrong.'
        end
      end
    end
  end

  def form_config
    {
      for: :user, # or whatever you're using
      method: :post,
      path: user_password_path(format: :json),
      delay: 500,
      success: {
        emit: 'request_password_success'
      },
      failure: {
        emit: 'request_password_failure'
      }
    }
  end

end

Passwords Edit Page

app/matestack/devise/pages/passwords/edit.rb

class Devise::Pages::Passwords::Edit < Matestack::Ui::Page

  def response
    bs_container class: "mt-5" do
      bs_row class: "mt-5", vertical: :center, horizontal: :center do
        bs_col lg: 4 do
          form_partial
        end
      end
    end
  end

  private

  def form_partial
    section class: "mt-5 rounded p-4 mb-4" do
      div class: "d-flex justify-content-between" do
        heading size: 2, text: 'Set new Password'
      end
      matestack_form form_config do
        form_input type: :hidden, key: :reset_password_token, init: params[:reset_password_token]
        div class: "mb-3 mt-4" do
          bs_form_input label: 'Password', key: :password, type: :password
        end
        div class: "mb-3" do
          bs_form_input label: 'Password confirmation', key: :password_confirmation, type: :password
        end
        div class: "mb-3" do
          bs_form_submit text: "Set new password"
        end
      end
      toggle show_on: 'password_failure', hide_after: 5000 do
        bs_alert variant: :danger do
          plain 'Something went wrong.'
        end
      end
    end
  end

  def form_config
    {
      for: :user, # or whatever you're using
      method: :put,
      path: user_password_path(format: :json),
      delay: 500,
      success: {
        redirect: {
          path: your_after_login_path# or whatever you're using
        }
      },
      failure: {
        emit: 'password_failure'
      }
    }
  end

end

Last updated