[issue #565] Added feedback form.

*  Added web pages to send request
*  Added mailer to send feedback message.
This commit is contained in:
George Vinogradov 2012-07-20 20:06:31 +04:00
parent f83a1ae6c2
commit d6c65a1626
15 changed files with 435 additions and 3 deletions

BIN
app/assets/images/check.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -1172,3 +1172,141 @@ table.tablesorter tr td.no_results {
text-align: center;
}
/* end */
article div.all {
width: 860px;
}
div.inner-columns {
width: 100%;
height: auto;
display: block;
}
div.inner-columns:after {
clear: both;
content: ".";
display: block;
height: 0;
visibility: hidden;
}
div.inner-columns.equal section {
width: 430px;
float: left;
}
section.first div {
width: 415px;
}
form.inline_errors span.error {
display: inline-block;
color: #FF5050;
}
div.form-actions {
text-align: right;
padding-bottom: 5px;
}
div.form-actions .button {
margin-right: 50px;
}
dl.form, div.form-actions {
margin: 15px 0;
}
dl.form > dt {
margin: 0 0 6px;
}
dl.form > dt label {
color: #333333;
font-size: 14px;
font-weight: bold;
position: relative;
}
dl.form > dd {
margin: 0;
padding: 0;
border: 0, none;
}
dl.form > dd > input[type="text"],
dl.form > dd > input[type="password"],
dl.form > dd > textarea {
width: 350px;
border: 1px solid #A0A0A0;
border-radius: 4px;
padding: 5px;
font-family: Tahoma, Geneva, Helvetica, sans-serif;
font-size: 12px;
}
dl.form > dd > input.error[type="text"],
dl.form > dd > input.error[type="password"],
dl.form > dd > textarea.error {
border-color: #FF8080;
}
dl.form > dd > input[type="text"],
dl.form > dd > input[type="password"] {
height: 16px;
}
dl.form > dd > textarea {
height: 110px;
}
ul.checklist {
font-size: 12px;
font-weight: bold;
margin: 20px 0;
padding: 0;
}
ul.checklist li {
background: image-url("check.png") no-repeat scroll 0 2px transparent;
list-style-type: none;
margin: 15px 0;
padding-left: 25px;
}
div.all.feedback_sended {
background: image-url("feedback.png") no-repeat scroll 49% 0 transparent;
height: 500px;
text-align: center;
}
article div.all.feedback_sended h1 {
font-size: 48px;
font-weight: normal;
margin: 165px 0 0;
padding: 0;
}
article div.all.feedback_sended h2 {
font-size: 18px;
font-weight: normal;
margin: 5px 0 0;
padding: 0;
}
article div.all.feedback_sended p.pages {
margin-top: 70px;
width: 280px;
}
article div.all.feedback_sended p.search {
margin-top: 80px;
width: 230px;
}
article div.all.feedback_sended p {
font-size: 14px;
margin: 0 auto;
padding: 0;
text-align: center;
}

View File

@ -0,0 +1,22 @@
# -*- encoding : utf-8 -*-
class ContactsController < ApplicationController
def new
@form = Feedback.new(current_user)
end
def create
@form = Feedback.new(params[:feedback])
if @form.perform_send
flash[:notice] = I18n.t("flash.contact.success")
redirect_to sended_contact_path
else
flash[:error] = I18n.t("flash.contact.error")
render :new and return
end
end
def sended
end
end

View File

@ -24,4 +24,5 @@ class PagesController < ApplicationController
def tos
end
end

View File

@ -14,6 +14,8 @@ module ApplicationHelper
'right bigpadding'
when controller_name == 'platforms' && action_name == 'clone'
'right middlepadding'
when controller_name == 'contacts' && action_name == 'sended'
'all feedback_sended'
else
content_for?(:sidebar) ? 'right' : 'all'
end

View File

@ -0,0 +1,38 @@
class FeedbackMailer < ActionMailer::Base
FBM_CONFIG = APP_CONFIG['feedback']
default :to => FBM_CONFIG['email'],
:cc => FBM_CONFIG['cc'],
:bcc => FBM_CONFIG['bcc']
include Resque::Mailer # send email async
def feedback_form_send(form_data)
@data = Feedback.new(form_data)
from = "#{@data.name} <#{@data.email}>"
subj = prepare_subject(@data.subject)
mail :from => from, :subject => subj
end
protected
def prepare_subject(subject)
res = ''
res << affix(FBM_CONFIG['subject_prefixes'])
res << subject
res << affix(FBM_CONFIG['subject_postfixes'])
# WOODOO magic. Change all space sequences to one space than remove trailing spaces.
res.gsub(/\s+/, ' ').gsub(/^\s|\s$/, '')
res
end
def affix(affixes)
res = ' '
res << Array(affixes).map{|e| "[#{e}]"}.join
res << ' '
res
end
end

108
app/models/feedback.rb Normal file
View File

@ -0,0 +1,108 @@
# This class is based on
# https://github.com/rails/rails/blob/4da6e1cce2833474034fda0cbb67b2cc35e828da/activerecord/lib/active_record/validations.rb
class Feedback
include ActiveModel::Conversion
include ActiveModel::Validations
include ActiveModel::Serializers::JSON
include ActiveModel::MassAssignmentSecurity
extend ActiveModel::Naming
attr_accessor :name, :email, :subject, :message
attr_accessible :name, :email, :subject, :message
validates :name, :presence => true
validates :email, :presence => true,
:format => { :with => /\A[^@]+@([^@\.]+\.)+[^@\.]+\z/, :allow_blank => false }
validates :subject, :presence => true
validates :message, :presence => true
def initialize(args = {}, options = {})
return args.dup if args.is_a? Feedback
if args.respond_to? :name and args.respond_to? :email
self.name, self.email = args.name, args.email
elsif args.respond_to? :each_pair
sanitize_for_mass_assignment(args, options[:as]).each_pair do |k, v|
send("#{k}=", v)
end
else
return false
end
end
# FIXME: Maybe rename to `save`?
def perform_send(options = {})
perform_validations(options) ? real_send : false
end
def perform_send!(options={})
perform_validations(options) ? real_send : raise(ActiveRecord::RecordInvalid.new(self))
end
def new_record?
true
end
def persisted?
false
end
def message_with_links
message.to_s.dup.auto_link
end
def attributes
%w{ name email subject message }.inject({}) do |h, e|
h.merge(e => send(e))
end
end
def to_s
str = "#<#{self.class} "
str += %w{ name email subject message }.inject('') do |s, e|
s << "#{e}: #{ send(e).inspect }, "; s
end
str[-2] = '>'
str[0..-1]
end
class << self
def create(attributes = nil, options = {}, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| create!(attr, options, &block) }
else
object = new(attributes, options)
yield(object) if block_given?
object.perform_send
object
end
end
def create!(attributes = nil, options = {}, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| create!(attr, options, &block) }
else
object = new(attributes, options)
yield(object) if block_given?
object.perform_send!
object
end
end
end
protected
def real_send
FeedbackMailer.feedback_form_send(self).deliver
end
def perform_validations(options={})
perform_validation = options[:validate] != false
perform_validation ? valid?(options[:context]) : true
end
end
Feedback.include_root_in_json = false

View File

@ -0,0 +1,44 @@
%h1= t("layout.contact.header")
%p= t("layout.contact.subheader")
%hr
.inner-columns.equal
%section.first
%div
= form_for @form, :url => contact_path, :html => {:class => 'inline_errors'} do |f|
%dl.form
%dt= f.label :name, t("activerecord.attributes.user.name")
%dd
= f.text_field :name, :placeholder => t("layout.contact.placeholders.name"),
:class => @form.errors.messages[:name].present? ? 'error' : ''
%dl.form
%dt= f.label :email, t("activerecord.attributes.user.email")
%dd
= f.text_field :email, :placeholder => t("layout.contact.placeholders.email"),
:class => @form.errors.messages[:email].present? ? 'error' : ''
%dl.form
%dt= f.label :subject, t("layout.contact.subject")
%dd
= f.text_field :subject, :placeholder => t("layout.contact.placeholders.subject"),
:class => @form.errors.messages[:subject].present? ? 'error' : ''
%dl.form
%dt= f.label :message, t("layout.contact.message")
%dd
= f.text_area :message, :placeholder => t("layout.contact.placeholders.message"),
:class => @form.errors.messages[:message].present? ? 'error' : ''
.form-actions
= f.submit t("layout.contact.send_request"), :class => 'button'
%section.last
%div
%h3= t("layout.contact.info.header")
%ul.checklist
- t("layout.contact.info.items").try(:each) do |item|
%li= item

View File

@ -0,0 +1,8 @@
%h1= t("layout.contact.thanks")
%h2= t("layout.contact.thanks_2")
%p.pages
= t("layout.contact.open_pages")
%p.search
= t("layout.contact.use_search")
.both

View File

@ -0,0 +1 @@
= simple_format @data.message_with_links

View File

@ -0,0 +1 @@
<%= @data.message.chomp %>

View File

@ -0,0 +1,32 @@
en:
layout:
contact:
header: Contact us
subheader: Were here to help with any questions or comments.
send_request: Send Request
thanks: Thanks for feedback!
thanks_2: Your request in progress.
open_pages: Please, continue your work
use_search: Try to find something cool?
placeholders:
name: Your name
email: user@example.com
subject: I want to say about ...
message: Problem description
subject: Subject
message: Message
info:
header: What's in a great support request?
items:
- "Be succinct, we'll ask if we need more info"
- "The name of the user, repository, build list etc youre having troubles with"
flash:
contact:
success: Request successfully sended
error: Sending request failed

View File

@ -0,0 +1,32 @@
ru:
layout:
contact:
header: Свяжитесь с нами
subheader: Мы готовы помочь вам с любыми вопросами и комментариями.
send_request: Отправить запрос
thanks: Спасибо за сообщение!
thanks_2: Мы уже начали работу.
open_pages: Продолжайте работу с системой
use_search: Воспользуйтесь поиском
placeholders:
name: Ваше имя
email: user@example.com
subject: Я хочу сказать о...
message: Описание проблемы
subject: Тема сообщения
message: Сообщение
info:
header: Каким должен быть хороший запрос?
items:
- "Будьте кратки. Если нам понадобится дополнительная информация, мы ее запросим."
- "Укажите, с чем у вас возникла проблема: имя пользователя, репозиторий, сборочный лист и т.д."
flash:
contact:
success: Запрос успешно отправлен
error: Не удалось отправить запрос

View File

@ -1,5 +1,10 @@
# -*- encoding : utf-8 -*-
Rosa::Application.routes.draw do
resource :contact, :only => [:new, :create, :sended] do
get '/' => 'contacts#new'
get :sended
end
devise_scope :users do
get '/users/auth/:provider' => 'users/omniauth_callbacks#passthru'
end
@ -7,9 +12,9 @@ Rosa::Application.routes.draw do
resources :search, :only => [:index]
get '/forbidden' => 'pages#forbidden', :as => 'forbidden'
get '/terms-of-service' => 'pages#tos', :as => 'tos'
get '/tour/:id' => 'pages#tour_inside', :as => 'tour_inside', :id => /projects|sources|builds/
get '/forbidden' => 'pages#forbidden', :as => 'forbidden'
get '/terms-of-service' => 'pages#tos', :as => 'tos'
get '/tour/:id' => 'pages#tour_inside', :as => 'tour_inside', :id => /projects|sources|builds/
get '/activity_feeds.:format' => 'activity_feeds#index', :as => 'atom_activity_feeds', :format => /atom/
if APP_CONFIG['anonymous_access']