Fixed login, new_password and invitation views.
This commit is contained in:
parent
f20ae890dd
commit
1708df3f6c
18 changed files with 200 additions and 122 deletions
|
@ -10,8 +10,10 @@ class InvitesController < ApplicationController
|
|||
def create
|
||||
@invite = Invite.new(params[:invite])
|
||||
if @invite.save
|
||||
Mailer.delay.invite(FoodsoftConfig.scope, @invite.id)
|
||||
redirect_to back_or_default_path, notice: "Benutzerin wurde erfolgreich eingeladen."
|
||||
else
|
||||
logger.debug "[debug] errors: #{@invite.errors.messages}"
|
||||
render action: :new
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
class LoginController < ApplicationController
|
||||
skip_before_filter :authenticate # no authentication since this is the login page
|
||||
before_filter :validate_token, :only => [:password, :update_password]
|
||||
before_filter :validate_token, :only => [:new_password, :update_password]
|
||||
|
||||
# Display the form to enter an email address requesting a token to set a new password.
|
||||
def forgot_password
|
||||
|
@ -14,7 +14,7 @@ class LoginController < ApplicationController
|
|||
user.reset_password_token = user.new_random_password(16)
|
||||
user.reset_password_expires = Time.now.advance(:days => 2)
|
||||
if user.save
|
||||
email = Mailer.reset_password(user).deliver
|
||||
Mailer.delay.reset_password(FoodsoftConfig.scope, user.id)
|
||||
logger.debug("Sent password reset email to #{user.email}.")
|
||||
end
|
||||
end
|
||||
|
@ -23,7 +23,7 @@ class LoginController < ApplicationController
|
|||
|
||||
# Set a new password with a token from the password reminder email.
|
||||
# Called with params :id => User.id and :token => User.reset_password_token to specify a new password.
|
||||
def password
|
||||
def new_password
|
||||
end
|
||||
|
||||
# Sets a new password.
|
||||
|
@ -36,13 +36,13 @@ class LoginController < ApplicationController
|
|||
@user.save
|
||||
redirect_to login_url, :notice => "Dein Passwort wurde aktualisiert. Du kannst Dich jetzt anmelden."
|
||||
else
|
||||
render :action => 'password'
|
||||
render :new_password
|
||||
end
|
||||
end
|
||||
|
||||
# Invited users.
|
||||
def invite
|
||||
@invite = Invite.find_by_token(params[:id])
|
||||
# For invited users.
|
||||
def accept_invitation
|
||||
@invite = Invite.find_by_token(params[:token])
|
||||
if (@invite.nil? || @invite.expires_at < Time.now)
|
||||
flash[:error] = "Deine Einladung ist nicht (mehr) gültig."
|
||||
render :action => 'login'
|
||||
|
@ -71,7 +71,7 @@ class LoginController < ApplicationController
|
|||
def validate_token
|
||||
@user = User.find_by_id_and_reset_password_token(params[:id], params[:token])
|
||||
if (@user.nil? || @user.reset_password_expires < Time.now)
|
||||
flash[:error] = "Ungültiger oder abgelaufener Token. Bitte versuch es erneut."
|
||||
flash.now.error = "Ungültiger oder abgelaufener Token. Bitte versuch es erneut."
|
||||
render :action => 'forgot_password'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -130,4 +130,17 @@ module ApplicationHelper
|
|||
:target => "_blank"
|
||||
end
|
||||
|
||||
def bootstrap_flash
|
||||
flash_messages = []
|
||||
flash.each do |type, message|
|
||||
type = :success if type == :notice
|
||||
type = :error if type == :alert
|
||||
text = content_tag(:div,
|
||||
content_tag(:button, raw("×"), :class => "close", "data-dismiss" => "alert") +
|
||||
message, :class => "alert fade in alert-#{type}")
|
||||
flash_messages << text if message
|
||||
end
|
||||
flash_messages.join("\n").html_safe
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -19,20 +19,22 @@ class Mailer < ActionMailer::Base
|
|||
|
||||
# Sends an email with instructions on how to reset the password.
|
||||
# Assumes user.setResetPasswordToken has been successfully called already.
|
||||
def reset_password(user)
|
||||
@user = user
|
||||
@link = url_for(:controller => "login", :action => "password", :id => user.id, :token => user.reset_password_token)
|
||||
def reset_password(foodcoop, user_id)
|
||||
set_foodcoop_scope(foodcoop)
|
||||
@user = User.find(user_id)
|
||||
@link = new_password_url(id: @user.id, token: @user.reset_password_token)
|
||||
|
||||
mail :to => user.email,
|
||||
:subject => "[#{FoodsoftConfig[:name]}] Neues Passwort für/ New password for #{user.nick}"
|
||||
mail :to => @user.email,
|
||||
:subject => "[#{FoodsoftConfig[:name]}] Neues Passwort für/ New password for #{@user.nick}"
|
||||
end
|
||||
|
||||
# Sends an invite email.
|
||||
def invite(invite)
|
||||
@invite = invite
|
||||
@link = url_for(:controller => "login", :action => "invite", :id => invite.token)
|
||||
def invite(foodcoop, invite_id)
|
||||
set_foodcoop_scope(foodcoop)
|
||||
@invite = Invite.find(invite_id)
|
||||
@link = accept_invitation_url(token: @invite.token)
|
||||
|
||||
mail :to => invite.email,
|
||||
mail :to => @invite.email,
|
||||
:subject => "Einladung in die Foodcoop #{FoodsoftConfig[:name]} - Invitation to the Foodcoop"
|
||||
end
|
||||
|
||||
|
@ -81,5 +83,11 @@ class Mailer < ActionMailer::Base
|
|||
mail :to => user.email,
|
||||
:subject => "[#{FoodsoftConfig[:name]}] \"#{task.name}\" braucht noch Leute!"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_foodcoop_scope(foodcoop)
|
||||
ActionMailer::Base.default_url_options[:foodcoop] = foodcoop
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -12,20 +12,16 @@ class Invite < ActiveRecord::Base
|
|||
validates_presence_of :expires_at
|
||||
validate :email_not_already_registered, :on => :create
|
||||
|
||||
before_validation :set_token_and_expires_at
|
||||
|
||||
protected
|
||||
|
||||
# Before validation, set token and expires_at.
|
||||
def before_validation
|
||||
def set_token_and_expires_at
|
||||
self.token = Digest::SHA1.hexdigest(Time.now.to_s + rand(100).to_s)
|
||||
self.expires_at = Time.now.advance(:days => 7)
|
||||
end
|
||||
|
||||
# Sends an email to the invited user.
|
||||
def after_create
|
||||
Mailer.invite(self).deliver
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Custom validation: check that email does not already belong to a registered user.
|
||||
|
|
29
app/views/layouts/_header.html.haml
Normal file
29
app/views/layouts/_header.html.haml
Normal file
|
@ -0,0 +1,29 @@
|
|||
!!! 5
|
||||
%html(lang="en")
|
||||
%head
|
||||
%meta(charset="utf-8")
|
||||
%meta(http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1")
|
||||
%meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
%title= content_for?(:title) ? yield(:title) : "Foodsoft"
|
||||
= csrf_meta_tags
|
||||
/ Le HTML5 shim, for IE6-8 support of HTML elements
|
||||
/[if lt IE 9]
|
||||
= javascript_include_tag "http://html5shim.googlecode.com/svn/trunk/html5.js"
|
||||
= stylesheet_link_tag "application", :media => "all"
|
||||
%link(href="images/apple-touch-icon-144x144.png" rel="apple-touch-icon-precomposed" sizes="144x144")
|
||||
%link(href="images/apple-touch-icon-114x114.png" rel="apple-touch-icon-precomposed" sizes="114x114")
|
||||
%link(href="images/apple-touch-icon-72x72.png" rel="apple-touch-icon-precomposed" sizes="72x72")
|
||||
%link(href="images/apple-touch-icon.png" rel="apple-touch-icon-precomposed")
|
||||
//%link(href="images/favicon.ico" rel="shortcut icon")
|
||||
|
||||
= yield(:head)
|
||||
|
||||
%body
|
||||
= yield
|
||||
|
||||
/
|
||||
Javascripts
|
||||
\==================================================
|
||||
/ Placed at the end of the document so the pages load faster
|
||||
= javascript_include_tag "application"
|
||||
= yield(:javascript)
|
|
@ -1,58 +1,35 @@
|
|||
!!! 5
|
||||
%html(lang="en")
|
||||
%head
|
||||
%meta(charset="utf-8")
|
||||
%meta(http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1")
|
||||
%meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
%title= content_for?(:title) ? yield(:title) : "Foodsoft"
|
||||
= csrf_meta_tags
|
||||
/ Le HTML5 shim, for IE6-8 support of HTML elements
|
||||
/[if lt IE 9]
|
||||
= javascript_include_tag "http://html5shim.googlecode.com/svn/trunk/html5.js"
|
||||
= stylesheet_link_tag "application", :media => "all"
|
||||
%link(href="images/apple-touch-icon-144x144.png" rel="apple-touch-icon-precomposed" sizes="144x144")
|
||||
%link(href="images/apple-touch-icon-114x114.png" rel="apple-touch-icon-precomposed" sizes="114x114")
|
||||
%link(href="images/apple-touch-icon-72x72.png" rel="apple-touch-icon-precomposed" sizes="72x72")
|
||||
%link(href="images/apple-touch-icon.png" rel="apple-touch-icon-precomposed")
|
||||
%link(href="images/favicon.ico" rel="shortcut icon")
|
||||
= render layout: 'layouts/header' do
|
||||
.row-fluid
|
||||
|
||||
.navbar.navbar-fixed-top
|
||||
.navbar-inner
|
||||
.container-fluid
|
||||
%a.btn.btn-navbar(data-target=".nav-collapse" data-toggle="collapse")
|
||||
%span.icon-bar
|
||||
%span.icon-bar
|
||||
%span.icon-bar
|
||||
= link_to 'Foodsoft', root_path(anchor: ''), class: 'brand'
|
||||
.container.nav-collapse
|
||||
= render_navigation expand_all: true, renderer: :bootstrap
|
||||
|
||||
%body
|
||||
.container-fluid
|
||||
.row-fluid
|
||||
|
||||
.navbar.navbar-fixed-top
|
||||
.navbar-inner
|
||||
.container-fluid
|
||||
%a.btn.btn-navbar(data-target=".nav-collapse" data-toggle="collapse")
|
||||
%span.icon-bar
|
||||
%span.icon-bar
|
||||
%span.icon-bar
|
||||
= link_to 'Foodsoft', root_path(anchor: ''), class: 'brand'
|
||||
.container.nav-collapse
|
||||
= render_navigation expand_all: true, renderer: :bootstrap
|
||||
|
||||
.container-fluid
|
||||
.row-fluid
|
||||
- if content_for?(:sidebar)
|
||||
.span2
|
||||
= yield(:sidebar)
|
||||
.span10
|
||||
- if show_title?
|
||||
.page-header
|
||||
%h1= yield(:title)
|
||||
= yield
|
||||
- else
|
||||
- if content_for?(:sidebar)
|
||||
.span2
|
||||
= yield(:sidebar)
|
||||
.span10
|
||||
= bootstrap_flash
|
||||
- if show_title?
|
||||
.page-header
|
||||
%h1= yield(:title)
|
||||
= yield
|
||||
|
||||
%footer
|
||||
%p
|
||||
Foodsoft, open source software to manage a non-profit food coop.
|
||||
- else
|
||||
= bootstrap_flash
|
||||
- if show_title?
|
||||
.page-header
|
||||
%h1= yield(:title)
|
||||
= yield
|
||||
|
||||
/
|
||||
Javascripts
|
||||
\==================================================
|
||||
/ Placed at the end of the document so the pages load faster
|
||||
= javascript_include_tag "application"
|
||||
%footer
|
||||
%p
|
||||
Foodsoft, open source software to manage a non-profit food coop.
|
||||
|
|
|
@ -1,19 +1,13 @@
|
|||
!!!
|
||||
%html
|
||||
%head
|
||||
%meta{"http-equiv" => "content-type", :content => "text/html;charset=UTF-8"}
|
||||
%title= "FoodSoft - " + (yield(:title) or controller.controller_name)
|
||||
= stylesheet_link_tag 'application'
|
||||
= javascript_include_tag 'application'
|
||||
= csrf_meta_tags
|
||||
= yield(:head)
|
||||
%body
|
||||
#login
|
||||
- if yield(:title)
|
||||
%h1= yield(:title)
|
||||
- flash.each do |name, msg|
|
||||
= content_tag :div, msg, :class => "flash #{name}"
|
||||
= yield
|
||||
#meta
|
||||
Foodcoop
|
||||
= link_to_if FoodsoftConfig[:homepage], FoodsoftConfig[:name], FoodsoftConfig[:homepage]
|
||||
= render layout: 'layouts/header' do
|
||||
.container
|
||||
.row
|
||||
.span6.offset3
|
||||
= bootstrap_flash
|
||||
- if show_title?
|
||||
.page-header
|
||||
%h1= yield(:title)
|
||||
= yield
|
||||
|
||||
%footer
|
||||
%p
|
||||
Foodsoft, open source software to manage a non-profit food coop.
|
|
@ -1,3 +1,7 @@
|
|||
- content_for :javascript do
|
||||
:javascript
|
||||
$('user_nick').focus();
|
||||
|
||||
- title "Einladung in die #{FoodsoftConfig[:name]}"
|
||||
%p
|
||||
Du bist eingeladen worden als Mitglied der Gruppe
|
||||
|
@ -12,7 +16,8 @@
|
|||
Gründen, weitergeben. Du kannst auch entscheiden, wieviel deiner persönlichen
|
||||
Daten für alle einsehbar sein sollen. 'Alle' bedeutet hier alle Foodcoop-Mitglieder.
|
||||
Die Administratoren haben aber jederzeit Zugriff auf deine Daten.
|
||||
= simple_form_for @user, url: {action: 'invite'} do |form|
|
||||
= simple_form_for @user, url: accept_invitation_path do |form|
|
||||
= render partial: 'shared/user_form_fields', locals: {f: form}
|
||||
= submit_tag "Absenden"
|
||||
= javascript_tag("$('user_nick').focus()")
|
||||
.form-actions
|
||||
= submit_tag "Foodsoft Account erstellen", class: 'btn'
|
||||
|
|
@ -7,5 +7,6 @@
|
|||
|
||||
= simple_form_for User.new, url: {action: 'reset_password'} do |form|
|
||||
= form.input :email
|
||||
= form.submit "Neues Passwort anfordern"
|
||||
= link_to "Abbrechen", :back
|
||||
.form-actions
|
||||
= form.submit "Neues Passwort anfordern", class: 'btn'
|
||||
= link_to "oder abbrechen", :back
|
|
@ -3,4 +3,5 @@
|
|||
= simple_form_for @user, :url => {:action => 'update_password', :id => @user.id, :token => @user.reset_password_token} do |form|
|
||||
= form.input :password
|
||||
= form.input :password_confirmation
|
||||
= form.submit
|
||||
.form-actions
|
||||
= form.submit 'Neues Passwort speichern', class: 'btn'
|
|
@ -1,7 +1,7 @@
|
|||
Hallo <%= @user.nick %>,
|
||||
|
||||
du (oder jemand anderes) hat auf der FoodSoft-Website ein neues Passwort angefordert.
|
||||
Um ein neues Passwort einzugeben, gehe zu: <%= @link %>
|
||||
Um ein neues Passwort einzugeben, gehe zu: <%= raw @link %>
|
||||
Dieser Link kann nur einmal aufgerufen werden und läuft am <%= I18n.l @user.reset_password_expires %> ab.
|
||||
Wenn du das Passwort nicht ändern möchtest oder diese Email nicht ausgelöst hast, brauchst du nichts zu tun. Dein bisheriges Passwort wurde nicht geändert.
|
||||
|
||||
|
@ -11,7 +11,7 @@ Grüße sendet die Foodsoft! :)
|
|||
|
||||
Hi <%= @user.nick %>,
|
||||
you have (or someone else has) requested a new password.
|
||||
In order to choose a new password follow this link: <%= @link %>
|
||||
In order to choose a new password follow this link: <%= raw @link %>
|
||||
This link works only once and expires on <%= I18n.l @user.reset_password_expires, locale: :en %>.
|
||||
If you don't want to change your password, just ignore this message. Your password hasn't been changed yet.
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
- content_for :head do
|
||||
- content_for :javascript do
|
||||
:javascript
|
||||
$(function() {
|
||||
$('#nick').focus();
|
||||
|
@ -7,21 +7,23 @@
|
|||
- title "FoodSoft login"
|
||||
|
||||
%noscript
|
||||
#javascript-warn.error(style="font-size:1.5em")
|
||||
.alert.alert-error
|
||||
Achtung, Cookies und Javascript müssen aktiviert sein!
|
||||
= link_to "NoScript", "http://noscript.net/"
|
||||
bitte abschalten.
|
||||
|
||||
#login-form.edit_form(style="width:25em")
|
||||
= form_tag sessions_path do
|
||||
%p
|
||||
%label{:for => 'user'} Benutzerin
|
||||
%br/
|
||||
= form_tag sessions_path, class: 'form-horizontal' do
|
||||
.control-group
|
||||
%label(for='nick' class='control-label') Benutzerin
|
||||
.controls
|
||||
= text_field_tag 'nick'
|
||||
%p
|
||||
%label{:for => 'password'} Passwort
|
||||
%br/
|
||||
|
||||
.control-group
|
||||
%label(for='password' class='control-label') Passwort
|
||||
.controls
|
||||
= password_field_tag 'password'
|
||||
= submit_tag "Anmelden"
|
||||
|
|
||||
= link_to "Passwort vergessen?", :controller => 'login', :action => 'forgot_password'
|
||||
|
||||
.control-group
|
||||
.controls
|
||||
= submit_tag "Anmelden", class: 'btn'
|
||||
= link_to "Passwort vergessen?", :controller => 'login', :action => 'forgot_password'
|
|
@ -3,11 +3,12 @@
|
|||
= f.input :last_name
|
||||
= f.input :email
|
||||
= f.input :phone
|
||||
= f.input :password, :required => @user.new_record?
|
||||
= f.input :password, :required => f.object.new_record?
|
||||
= f.input :password_confirmation
|
||||
- for setting in User::setting_keys.keys
|
||||
.input.boolean
|
||||
= check_box_tag "user[setting_attributes][#{setting}]",
|
||||
'1', @user.settings[setting] == '1' || @user.settings_default(setting),
|
||||
:class => 'boolean'
|
||||
%label.boolean{:for => "user[setting_attributes][#{setting}]"}= h User::setting_keys[setting]
|
||||
.control-group
|
||||
.controls
|
||||
- for setting in User::setting_keys.keys
|
||||
%label.checkbox{:for => "user[setting_attributes][#{setting}]"}
|
||||
= check_box_tag "user[setting_attributes][#{setting}]", '1',
|
||||
f.object.settings[setting] == '1' || f.object.settings_default(setting)
|
||||
= User::setting_keys[setting]
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
Delayed::Worker.max_attempts = 5
|
||||
Delayed::Worker.delay_jobs = !Rails.env.test?
|
||||
Delayed::Worker.destroy_failed_jobs = false
|
||||
|
|
|
@ -98,7 +98,7 @@ SimpleForm.setup do |config|
|
|||
config.label_class = 'control-label'
|
||||
|
||||
# You can define the class to use on all forms. Default is simple_form.
|
||||
# config.form_class = :simple_form
|
||||
config.form_class = 'form-horizontal'
|
||||
|
||||
# You can define which elements should obtain additional classes
|
||||
# config.generate_additional_classes_for = [:wrapper, :label, :input]
|
||||
|
@ -108,7 +108,7 @@ SimpleForm.setup do |config|
|
|||
|
||||
# Tell browsers whether to use default HTML5 validations (novalidate option).
|
||||
# Default is enabled.
|
||||
config.browser_validations = false
|
||||
config.browser_validations = true
|
||||
|
||||
# Collection of methods to detect if a file type was given.
|
||||
# config.file_methods = [ :mounted_as, :file?, :public_filename ]
|
||||
|
|
45
config/initializers/simple_form_bootstrap.rb
Normal file
45
config/initializers/simple_form_bootstrap.rb
Normal file
|
@ -0,0 +1,45 @@
|
|||
# Use this setup block to configure all options available in SimpleForm.
|
||||
SimpleForm.setup do |config|
|
||||
config.wrappers :bootstrap, :tag => 'div', :class => 'control-group', :error_class => 'error' do |b|
|
||||
b.use :html5
|
||||
b.use :placeholder
|
||||
b.use :label
|
||||
b.wrapper :tag => 'div', :class => 'controls' do |ba|
|
||||
ba.use :input
|
||||
ba.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
|
||||
ba.use :hint, :wrap_with => { :tag => 'p', :class => 'help-block' }
|
||||
end
|
||||
end
|
||||
|
||||
config.wrappers :prepend, :tag => 'div', :class => "control-group", :error_class => 'error' do |b|
|
||||
b.use :html5
|
||||
b.use :placeholder
|
||||
b.use :label
|
||||
b.wrapper :tag => 'div', :class => 'controls' do |input|
|
||||
input.wrapper :tag => 'div', :class => 'input-prepend' do |prepend|
|
||||
prepend.use :input
|
||||
end
|
||||
input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' }
|
||||
input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
|
||||
end
|
||||
end
|
||||
|
||||
config.wrappers :append, :tag => 'div', :class => "control-group", :error_class => 'error' do |b|
|
||||
b.use :html5
|
||||
b.use :placeholder
|
||||
b.use :label
|
||||
b.wrapper :tag => 'div', :class => 'controls' do |input|
|
||||
input.wrapper :tag => 'div', :class => 'input-append' do |append|
|
||||
append.use :input
|
||||
end
|
||||
input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' }
|
||||
input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
|
||||
end
|
||||
end
|
||||
|
||||
# Wrappers for forms and inputs using the Twitter Bootstrap toolkit.
|
||||
# Check the Bootstrap docs (http://twitter.github.com/bootstrap)
|
||||
# to learn about the different styles for forms and inputs,
|
||||
# buttons and other elements.
|
||||
config.default_wrapper = :bootstrap
|
||||
end
|
|
@ -17,6 +17,8 @@ Foodsoft::Application.routes.draw do
|
|||
|
||||
match '/login' => 'sessions#new', :as => 'login'
|
||||
match '/logout' => 'sessions#destroy', :as => 'logout'
|
||||
get '/login/new_password' => 'login#new_password', as: :new_password
|
||||
match '/login/accept_invitation/:token' => 'login#accept_invitation', as: :accept_invitation
|
||||
resources :sessions, :only => [:new, :create, :destroy]
|
||||
|
||||
########### User specific
|
||||
|
@ -174,6 +176,7 @@ Foodsoft::Application.routes.draw do
|
|||
|
||||
resources :users, :only => [:index]
|
||||
|
||||
# TODO: This is very error prone. Better deactivate this catch all route
|
||||
match ':controller(/:action(/:id))(.:format)'
|
||||
|
||||
end # End of /:foodcoop scope
|
||||
|
|
Loading…
Reference in a new issue