Browse Source

google sign up / sign in integration

pull/1/head
Nicolae Claudius 11 years ago
parent
commit
0217d90a9f
  1. 3
      Gemfile
  2. 21
      Gemfile.lock
  3. 2
      app/assets/stylesheets/application.css.scss
  4. 33
      app/assets/stylesheets/pages/home.css.scss
  5. 6
      app/assets/stylesheets/theme/overrides.css.scss
  6. 6
      app/controllers/users/authentications_controller.rb
  7. 78
      app/controllers/users/omniauth_callbacks_controller.rb
  8. 15
      app/helpers/users/authentications_helper.rb
  9. 3
      app/models/authentication.rb
  10. 5
      app/models/user.rb
  11. 3
      app/models/user_ability.rb
  12. 5
      app/views/public/pages/home.html.erb
  13. 10
      app/views/users/confirmations/new.html.erb
  14. 10
      app/views/users/passwords/new.html.erb
  15. 12
      app/views/users/registrations/_new.html.erb
  16. 8
      app/views/users/registrations/edit.html.erb
  17. 9
      app/views/users/registrations/new.html.erb
  18. 25
      app/views/users/sessions/new.html.erb
  19. 8
      app/views/users/shared/_links.erb
  20. 10
      app/views/users/unlocks/new.html.erb
  21. 8
      config/initializers/devise.rb
  22. 7
      config/routes.rb
  23. 2
      config/settings.sample.yml
  24. 14
      db/migrate/20140102165905_create_authentications.rb
  25. 15
      db/schema.rb
  26. 15
      spec/routing/users/authentications_routing_spec.rb
  27. 57
      vendor/assets/stylesheets/bootstrap-social.css

3
Gemfile

@ -3,6 +3,8 @@ source 'http://rubygems.org'
gem 'rails', '4.0.2'
gem 'mysql2', '~> 0.3.13'
gem 'devise', '~> 3.2.2'
gem 'omniauth', '~> 1.1.4'
gem 'omniauth-google-apps', '~> 0.1.0'
gem 'cancan', '= 1.6.7'
gem 'squeel', '~> 1.1.0'
gem 'sentient_model', '~> 1.0.4'
@ -24,6 +26,7 @@ gem 'unicorn', '~> 4.6.3'
gem 'sass-rails', '~> 4.0.0'
gem 'compass-rails', '~> 1.1.0'
gem 'bootstrap-sass', '~> 3.0.3.0'
gem 'font-awesome-rails', '~> 4.0.3.0'
gem 'webshims-rails', '~> 1.11.1'
gem 'detect_timezone_rails', '~> 0.0.3'
gem 'nprogress-rails', '~> 0.1.2.2'

21
Gemfile.lock

@ -127,6 +127,7 @@ GEM
fssm (0.2.10)
haml (4.0.4)
tilt
hashie (2.0.5)
hike (1.2.3)
i18n (0.6.9)
jquery-rails (3.0.4)
@ -164,6 +165,17 @@ GEM
nprogress-rails (0.1.2.3)
oily_png (1.1.0)
chunky_png (~> 1.2.7)
omniauth (1.1.4)
hashie (>= 1.2, < 3)
rack
omniauth-google-apps (0.1.0)
omniauth (~> 1.0)
omniauth-openid (~> 1.0)
ruby-openid (~> 2.3.0)
ruby-openid-apps-discovery (~> 1.2.0)
omniauth-openid (1.0.1)
omniauth (~> 1.0)
rack-openid (~> 1.3.1)
orm_adapter (0.5.0)
polyamorous (0.6.4)
activerecord (>= 3.0)
@ -171,6 +183,9 @@ GEM
quiet_assets (1.0.2)
railties (>= 3.1, < 5.0)
rack (1.5.2)
rack-openid (1.3.1)
rack (>= 1.1.0)
ruby-openid (>= 2.1.8)
rack-pjax (0.7.0)
nokogiri (~> 1.5)
rack (~> 1.3)
@ -223,6 +238,9 @@ GEM
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
ruby-openid (2.3.0)
ruby-openid-apps-discovery (1.2.0)
ruby-openid (>= 2.1.7)
safe_yaml (0.9.7)
sass (3.2.13)
sass-rails (4.0.1)
@ -301,6 +319,7 @@ DEPENDENCIES
devise (~> 3.2.2)
factory_girl_rails (~> 4.3.0)
faker (~> 1.2.0)
font-awesome-rails (~> 4.0.3.0)
jquery-rails (~> 3.0.4)
jquery-ui-rails (~> 4.1.0)
json (~> 1.8.0)
@ -311,6 +330,8 @@ DEPENDENCIES
nilify_blanks (~> 1.0.2)
nprogress-rails (~> 0.1.2.2)
oily_png (~> 1.1.0)
omniauth (~> 1.1.4)
omniauth-google-apps (~> 0.1.0)
quiet_assets (~> 1.0.1)
rails (= 4.0.2)
rails-settings-cached (~> 0.3.1)

2
app/assets/stylesheets/application.css.scss

@ -5,6 +5,8 @@
*= require nprogress
*= require animate
*= require bootstrap-social
*= require font-awesome
*= require_self
*/

33
app/assets/stylesheets/pages/home.css.scss

@ -1,15 +1,6 @@
@import "compass/css3/images";
@import "compass/css3/border-radius";
.home-sign-up {
h3 {
margin-top: 0;
}
.actions {
margin-top: 2em;
}
}
.page-home-header {
@include background(radial-gradient(color-stops(#fff, #c1f1ff), bottom));
border-bottom: 1px solid #B9DCFF;
@ -39,3 +30,27 @@
}
}
// text-separator
.text-separator {
overflow: hidden;
text-align: center;
margin: 20px 0 20px 0;
}
.text-separator:before,
.text-separator:after {
background-color: #abe098;
content: "";
display: inline-block;
height: 1px;
position: relative;
vertical-align: middle;
width: 50%;
}
.text-separator:before {
right: 0.5em;
margin-left: -50%;
}
.text-separator:after {
left: 0.5em;
margin-right: -50%;
}

6
app/assets/stylesheets/theme/overrides.css.scss

@ -28,3 +28,9 @@
padding: 9px 14px;
font-weight: normal;
}
.btn-social:hover {
&:hover, &:focus {
color: $btn-primary-color;
}
}

6
app/controllers/users/authentications_controller.rb

@ -0,0 +1,6 @@
class Users::AuthenticationsController < UsersController
active_scaffold :authentication do |conf|
conf.list.columns = [:provider]
conf.actions.exclude :create, :search, :update, :show
end
end

78
app/controllers/users/omniauth_callbacks_controller.rb

@ -0,0 +1,78 @@
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_apps
oauthorize 'google_apps'
end
protected
def oauthorize(provider)
@user = find_for_oauth(provider)
return unless @user
if @user.active_for_authentication?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", kind: provider.camelcase
session["devise.google_apps_data"] = env["omniauth.auth"]
@user.remember_me! if session.delete(:user_remember_me) == "1"
end
if user_signed_in?
redirect_to :back
else
sign_in_and_redirect @user, event: :authentication
end
end
def find_for_oauth(provider)
user = if resource then resource
elsif email then find_or_create_by_email(email)
elsif uid then find_or_create_by_uid(uid)
else raise "Bad provider data: #{auth.inspect}"
end
authentication = user.authentications.where(provider: provider).first
if authentication.nil?
authentication_attrs = authorization_attrs.merge(provider: provider)
authentication = user.authentications.build(authentication_attrs)
user.authentications << authentication
end
return user
end
def find_or_create_by_uid(uid)
auth = Authentication.where(uid: uid).first
return auth ? auth.user : make_user
end
def find_or_create_by_email(email)
user = User.where(email: email).first
return user ? user : make_user
end
def make_user
return current_user if user_signed_in?
user = User.new(user_attrs.merge(password: Devise.friendly_token[0,20]))
user.skip_confirmation!
user.save!(validate: false)
return user
end
def auth; env["omniauth.auth"] end
def uid; @uid ||= auth['uid'] rescue nil end
def email; @email ||= auth['info']['email'] rescue nil end
def authorization_attrs
@authorization_attrs ||= {
uid: uid,
token: auth['credentials']['token'],
secret: auth['credentials']['secret'],
name: auth['info']['name']
}
end
def user_attrs
@user_attrs ||= { email: email, full_name: auth['info']['name'] }
end
def handle_unverified_request; true end
end

15
app/helpers/users/authentications_helper.rb

@ -0,0 +1,15 @@
module Users::AuthenticationsHelper
def authentication_provider_column(record, column)
case record.provider
when 'google_apps'
ret = <<-DOC
<span class="btn btn-block btn-social btn-google-plus">
<i class="fa fa-google-plus"></i> Google
</span>
DOC
ret.html_safe
else
record.provider.camelcase
end
end
end

3
app/models/authentication.rb

@ -0,0 +1,3 @@
class Authentication < ActiveRecord::Base
belongs_to :user, :inverse_of => :authentications
end

5
app/models/user.rb

@ -12,7 +12,9 @@ class User < ActiveRecord::Base
:trackable,
:validatable,
:confirmable,
:lockable
:lockable,
:omniauthable,
:omniauth_providers => [:google_apps]
validates :full_name, :presence => true
@ -20,6 +22,7 @@ class User < ActiveRecord::Base
# attr_accessible :email, :password, :password_confirmation, :remember_me,
# :full_name
has_many :authentications, :inverse_of => :user, :dependent => :destroy
has_many :domains, :inverse_of => :user, :dependent => :destroy
has_many :records, :inverse_of => :user, :dependent => :destroy
has_many :permissions, :inverse_of => :user, :dependent => :destroy

3
app/models/user_ability.rb

@ -32,6 +32,9 @@ class UserAbility
# can manage permissions for his domains
can CRUD, Permission, :domain => {:user_id => user.id}
can :crud_permissions, Domain, :user_id => user.id
# can manage his authentications
can CRUD, Authentication, :user_id => user.id
end
def sharing_abilities

5
app/views/public/pages/home.html.erb

@ -12,10 +12,7 @@
<div class="container">
<div class="row">
<div class="col-lg-4 home-sign-up">
<h3>Use it yourself, it's free!</h3>
<p>In a few seconds you're done!</p>
<br>
<div class="col-lg-4">
<%= render 'users/registrations/new', wrapper: :inlabel,
resource_name: :user, resource: User.new %>
<br class="hidden-lg">

10
app/views/users/confirmations/new.html.erb

@ -1,6 +1,9 @@
<%= render :partial => "users/shared/links" %>
<%= simple_form_for(resource,
<div class="row">
<div class="col-lg-offset-4 col-lg-4 well">
<%= simple_form_for(resource,
as: resource_name,
url: confirmation_path(resource_name),
method: :post
@ -11,4 +14,7 @@
<%= f.input :email, required: true %>
<%= f.submit "Resend confirmation instructions", class: 'btn btn-primary' %>
<% end %>
<% end %>
</div>
</div>

10
app/views/users/passwords/new.html.erb

@ -1,6 +1,9 @@
<%= render :partial => "users/shared/links" %>
<%= simple_form_for(resource,
<div class="row">
<div class="col-lg-offset-4 col-lg-4 well">
<%= simple_form_for(resource,
as: resource_name,
url: password_path(resource_name),
method: :post
@ -11,4 +14,7 @@
<%= f.input :email, required: true %>
<%= f.submit "Send me reset password instructions", class: 'btn btn-primary' %>
<% end %>
<% end %>
</div>
</div>

12
app/views/users/registrations/_new.html.erb

@ -6,6 +6,16 @@
options[:wrapper] = wrapper if defined? wrapper
%>
<p>
<%= link_to user_omniauth_authorize_path(:google_apps),
class: 'btn btn-block btn-social btn-google-plus',
'data-no-turbolink' => true do %>
<i class="fa fa-google-plus"></i> Sign up with Google
<% end %>
</p>
<h5 class="text-info text-center text-separator">Or use the form below</h5>
<%= simple_form_for(resource, options) do |f| %>
<%= devise_error_messages! %>
@ -18,7 +28,7 @@
<%= f.submit "Sign up", class: 'btn btn-primary' %>
<small>
or <%= link_to 'Sign in', new_user_session_path %>
/ <%= link_to 'Forgot your password?', new_password_path(resource_name) %>
/ <%= link_to 'Forgot password', new_password_path(resource_name) %>
</small>
</div>
<% end %>

8
app/views/users/registrations/edit.html.erb

@ -3,7 +3,7 @@
</div>
<div class="row">
<div class="col-lg-8">
<div class="col-lg-5">
<%= simple_form_for(resource,
as: resource_name,
url: registration_path(resource_name),
@ -26,8 +26,12 @@
<%= f.submit "Update", class: 'btn btn-primary' %>
<% end %>
</div>
<div class="col-lg-4">
<div class="col-lg-offset-1 col-lg-6">
<br class="hidden-lg">
<% if current_user.authentications.exists? %>
<%= render active_scaffold: "users/authentications" %>
<br><br>
<% end %>
<div class="alert alert-warning text-center">
<h4>Delete account</h4>
<p>

9
app/views/users/registrations/new.html.erb

@ -1,4 +1,9 @@
<%= render :partial => "users/shared/links" %>
<%= render 'users/shared/links' %>
<%= render 'users/registrations/new' %>
<div class="row">
<div class="col-lg-offset-4 col-lg-4 well">
<%= render 'users/registrations/new' %>
</div>
</div>

25
app/views/users/sessions/new.html.erb

@ -1,6 +1,19 @@
<%= render :partial => "users/shared/links" %>
<%= render 'users/shared/links' %>
<%= simple_form_for(resource,
<div class="row">
<div class="col-lg-offset-4 col-lg-4 well">
<p>
<%= link_to user_omniauth_authorize_path(:google_apps),
class: 'btn btn-block btn-social btn-google-plus',
'data-no-turbolink' => true do %>
<i class="fa fa-google-plus"></i> Sign in with Google
<% end %>
</p>
<h5 class="text-info text-center text-separator">Or use the form below</h5>
<%= simple_form_for(resource,
as: resource_name,
url: session_path(resource_name)
) do |f| %>
@ -14,6 +27,10 @@
<%= f.submit "Sign in", class: 'btn btn-primary' %>
<small>
or <%= link_to "Sign up", new_registration_path(resource_name) %>
/ <%= link_to 'Forgot your password?', new_password_path(resource_name) %>
/ <%= link_to 'Forgot password', new_password_path(resource_name) %>
</small>
<% end %>
<% end %>
</div>
</div>

8
app/views/users/shared/_links.erb

@ -1,10 +1,10 @@
<br />
<% semantic_navigation :devise_nav, html: { class: 'nav nav-tabs' } do |n| %>
<% semantic_navigation :devise_nav, html: { class: 'nav nav-tabs nav-justified' } do |n| %>
<%= n.item "Sign in", link: new_session_path(resource_name) %>
<%= n.item "Sign up", link: new_registration_path(resource_name) %>
<%= n.item "Forgot your password?", link: new_password_path(resource_name),
<%= n.item "Forgot password", link: new_password_path(resource_name),
highlights_on: [{ controller: 'devise/passwords' }] %>
<%= n.item "Didn't receive confirmation instructions?", link: new_confirmation_path(resource_name) %>
<%= n.item "Didn't receive unlock instructions?", link: new_unlock_path(resource_name) %>
<%= n.item "Resend confirmation instructions", link: new_confirmation_path(resource_name) %>
<%= n.item "Resend unlock instructions", link: new_unlock_path(resource_name) %>
<% end %>
</br>

10
app/views/users/unlocks/new.html.erb

@ -1,6 +1,9 @@
<%= render :partial => "users/shared/links" %>
<%= simple_form_for(resource,
<div class="row">
<div class="col-lg-offset-4 col-lg-4 well">
<%= simple_form_for(resource,
as: resource_name,
url: unlock_path(resource_name),
method: :post
@ -11,4 +14,7 @@
<%= f.input :email, required: true %>
<%= f.submit "Resend unlock instructions", class: 'btn btn-primary' %>
<% end %>
<% end %>
</div>
</div>

8
config/initializers/devise.rb

@ -1,3 +1,5 @@
require 'openid/store/filesystem'
# Use this hook to configure devise mailer, warden hooks and so forth. The first
# four configuration values can also be set straight in your models.
Devise.setup do |config|
@ -193,6 +195,12 @@ Devise.setup do |config|
# Add a new OmniAuth provider. Check the wiki for more information on setting
# up on your models and hooks.
# config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo'
# config.omniauth :open_id, name: 'user',
# identifier: 'https://www.google.com/accounts/o8/site-xrds?hd=' + Settings.domain,
# store: OpenID::Store::Filesystem.new('/tmp')
config.omniauth :google_apps,
store: OpenID::Store::Filesystem.new('/tmp'),
domain: 'gmail.com'
# ==> Warden configuration
# If you want to use other strategies, that are not supported by Devise, or

7
config/routes.rb

@ -5,11 +5,16 @@ Entrydns::Application.routes.draw do
devise_for :admins
devise_for :users, controllers: {
registrations: 'users/registrations'
registrations: 'users/registrations',
omniauth_callbacks: 'users/omniauth_callbacks'
}
scope module: 'users' do
resources :authentications do
as_routes
end
resources :domains do
as_routes
end

2
config/settings.sample.yml

@ -1,3 +1,5 @@
domain: entrydns.net
# framework
secret_key_base: 0ce1f02a4b3fc4d1a1c8d22973b21e8589e9314dc338294953f0b985e3f44f12c8af74f2d9ba6f7c7bdb736c4efc5ea3f8135e23b1a036d033cd23331383ac75

14
db/migrate/20140102165905_create_authentications.rb

@ -0,0 +1,14 @@
class CreateAuthentications < ActiveRecord::Migration
def change
create_table :authentications do |t|
t.references :user, index: true
t.string :provider, null: false
t.string :uid, null: false
t.string :token
t.string :secret
t.string :name
t.timestamps
end
end
end

15
db/schema.rb

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20130918144521) do
ActiveRecord::Schema.define(version: 20140102165905) do
create_table "admins", force: true do |t|
t.string "email", default: "", null: false
@ -32,6 +32,19 @@ ActiveRecord::Schema.define(version: 20130918144521) do
add_index "admins", ["email"], name: "index_admins_on_email", unique: true, using: :btree
add_index "admins", ["reset_password_token"], name: "index_admins_on_reset_password_token", unique: true, using: :btree
create_table "authentications", force: true do |t|
t.integer "user_id"
t.string "provider", null: false
t.string "uid", null: false
t.string "token"
t.string "secret"
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "authentications", ["user_id"], name: "index_authentications_on_user_id", using: :btree
create_table "blacklisted_domains", force: true do |t|
t.string "name"
t.datetime "created_at"

15
spec/routing/users/authentications_routing_spec.rb

@ -0,0 +1,15 @@
require "spec_helper"
describe Users::AuthenticationsController do
describe "routing" do
it "routes to #index" do
get("/authentications").should route_to("users/authentications#index")
end
it "routes to #destroy" do
delete("/authentications/1").should route_to("users/authentications#destroy", :id => "1")
end
end
end

57
vendor/assets/stylesheets/bootstrap-social.css vendored

@ -0,0 +1,57 @@
/*
* Social Buttons for Bootstrap
*
* Copyright 2013 Panayiotis Lipiridis
* Licensed under the MIT License
*
* https://github.com/lipis/bootstrap-social
*/
.btn-social{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.btn-social :first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)}
.btn-social.btn-lg{padding-left:61px}.btn-social.btn-lg :first-child{line-height:45px;width:45px;font-size:1.8em}
.btn-social.btn-sm{padding-left:38px}.btn-social.btn-sm :first-child{line-height:28px;width:28px;font-size:1.4em}
.btn-social.btn-xs{padding-left:30px}.btn-social.btn-xs :first-child{line-height:20px;width:20px;font-size:1.2em}
.btn-social-icon{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;height:34px;width:34px;padding:0}.btn-social-icon :first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)}
.btn-social-icon.btn-lg{padding-left:61px}.btn-social-icon.btn-lg :first-child{line-height:45px;width:45px;font-size:1.8em}
.btn-social-icon.btn-sm{padding-left:38px}.btn-social-icon.btn-sm :first-child{line-height:28px;width:28px;font-size:1.4em}
.btn-social-icon.btn-xs{padding-left:30px}.btn-social-icon.btn-xs :first-child{line-height:20px;width:20px;font-size:1.2em}
.btn-social-icon :first-child{border:none;text-align:center;width:100% !important}
.btn-social-icon.btn-lg{height:45px;width:45px;padding-left:0;padding-right:0}
.btn-social-icon.btn-sm{height:30px;width:30px;padding-left:0;padding-right:0}
.btn-social-icon.btn-xs{height:22px;width:22px;padding-left:0;padding-right:0}
.btn-bitbucket{color:#fff;background-color:#205081;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:hover,.btn-bitbucket:focus,.btn-bitbucket:active,.btn-bitbucket.active,.open .dropdown-toggle.btn-bitbucket{color:#fff;background-color:#183c60;border-color:rgba(0,0,0,0.2)}
.btn-bitbucket:active,.btn-bitbucket.active,.open .dropdown-toggle.btn-bitbucket{background-image:none}
.btn-bitbucket.disabled,.btn-bitbucket[disabled],fieldset[disabled] .btn-bitbucket,.btn-bitbucket.disabled:hover,.btn-bitbucket[disabled]:hover,fieldset[disabled] .btn-bitbucket:hover,.btn-bitbucket.disabled:focus,.btn-bitbucket[disabled]:focus,fieldset[disabled] .btn-bitbucket:focus,.btn-bitbucket.disabled:active,.btn-bitbucket[disabled]:active,fieldset[disabled] .btn-bitbucket:active,.btn-bitbucket.disabled.active,.btn-bitbucket[disabled].active,fieldset[disabled] .btn-bitbucket.active{background-color:#205081;border-color:rgba(0,0,0,0.2)}
.btn-dropbox{color:#fff;background-color:#1087dd;border-color:rgba(0,0,0,0.2)}.btn-dropbox:hover,.btn-dropbox:focus,.btn-dropbox:active,.btn-dropbox.active,.open .dropdown-toggle.btn-dropbox{color:#fff;background-color:#0d70b7;border-color:rgba(0,0,0,0.2)}
.btn-dropbox:active,.btn-dropbox.active,.open .dropdown-toggle.btn-dropbox{background-image:none}
.btn-dropbox.disabled,.btn-dropbox[disabled],fieldset[disabled] .btn-dropbox,.btn-dropbox.disabled:hover,.btn-dropbox[disabled]:hover,fieldset[disabled] .btn-dropbox:hover,.btn-dropbox.disabled:focus,.btn-dropbox[disabled]:focus,fieldset[disabled] .btn-dropbox:focus,.btn-dropbox.disabled:active,.btn-dropbox[disabled]:active,fieldset[disabled] .btn-dropbox:active,.btn-dropbox.disabled.active,.btn-dropbox[disabled].active,fieldset[disabled] .btn-dropbox.active{background-color:#1087dd;border-color:rgba(0,0,0,0.2)}
.btn-facebook{color:#fff;background-color:#3b5998;border-color:rgba(0,0,0,0.2)}.btn-facebook:hover,.btn-facebook:focus,.btn-facebook:active,.btn-facebook.active,.open .dropdown-toggle.btn-facebook{color:#fff;background-color:#30487b;border-color:rgba(0,0,0,0.2)}
.btn-facebook:active,.btn-facebook.active,.open .dropdown-toggle.btn-facebook{background-image:none}
.btn-facebook.disabled,.btn-facebook[disabled],fieldset[disabled] .btn-facebook,.btn-facebook.disabled:hover,.btn-facebook[disabled]:hover,fieldset[disabled] .btn-facebook:hover,.btn-facebook.disabled:focus,.btn-facebook[disabled]:focus,fieldset[disabled] .btn-facebook:focus,.btn-facebook.disabled:active,.btn-facebook[disabled]:active,fieldset[disabled] .btn-facebook:active,.btn-facebook.disabled.active,.btn-facebook[disabled].active,fieldset[disabled] .btn-facebook.active{background-color:#3b5998;border-color:rgba(0,0,0,0.2)}
.btn-flickr{color:#fff;background-color:#ff0084;border-color:rgba(0,0,0,0.2)}.btn-flickr:hover,.btn-flickr:focus,.btn-flickr:active,.btn-flickr.active,.open .dropdown-toggle.btn-flickr{color:#fff;background-color:#d6006f;border-color:rgba(0,0,0,0.2)}
.btn-flickr:active,.btn-flickr.active,.open .dropdown-toggle.btn-flickr{background-image:none}
.btn-flickr.disabled,.btn-flickr[disabled],fieldset[disabled] .btn-flickr,.btn-flickr.disabled:hover,.btn-flickr[disabled]:hover,fieldset[disabled] .btn-flickr:hover,.btn-flickr.disabled:focus,.btn-flickr[disabled]:focus,fieldset[disabled] .btn-flickr:focus,.btn-flickr.disabled:active,.btn-flickr[disabled]:active,fieldset[disabled] .btn-flickr:active,.btn-flickr.disabled.active,.btn-flickr[disabled].active,fieldset[disabled] .btn-flickr.active{background-color:#ff0084;border-color:rgba(0,0,0,0.2)}
.btn-github{color:#fff;background-color:#444;border-color:rgba(0,0,0,0.2)}.btn-github:hover,.btn-github:focus,.btn-github:active,.btn-github.active,.open .dropdown-toggle.btn-github{color:#fff;background-color:#303030;border-color:rgba(0,0,0,0.2)}
.btn-github:active,.btn-github.active,.open .dropdown-toggle.btn-github{background-image:none}
.btn-github.disabled,.btn-github[disabled],fieldset[disabled] .btn-github,.btn-github.disabled:hover,.btn-github[disabled]:hover,fieldset[disabled] .btn-github:hover,.btn-github.disabled:focus,.btn-github[disabled]:focus,fieldset[disabled] .btn-github:focus,.btn-github.disabled:active,.btn-github[disabled]:active,fieldset[disabled] .btn-github:active,.btn-github.disabled.active,.btn-github[disabled].active,fieldset[disabled] .btn-github.active{background-color:#444;border-color:rgba(0,0,0,0.2)}
.btn-google-plus{color:#fff;background-color:#dd4b39;border-color:rgba(0,0,0,0.2)}.btn-google-plus:hover,.btn-google-plus:focus,.btn-google-plus:active,.btn-google-plus.active,.open .dropdown-toggle.btn-google-plus{color:#fff;background-color:#ca3523;border-color:rgba(0,0,0,0.2)}
.btn-google-plus:active,.btn-google-plus.active,.open .dropdown-toggle.btn-google-plus{background-image:none}
.btn-google-plus.disabled,.btn-google-plus[disabled],fieldset[disabled] .btn-google-plus,.btn-google-plus.disabled:hover,.btn-google-plus[disabled]:hover,fieldset[disabled] .btn-google-plus:hover,.btn-google-plus.disabled:focus,.btn-google-plus[disabled]:focus,fieldset[disabled] .btn-google-plus:focus,.btn-google-plus.disabled:active,.btn-google-plus[disabled]:active,fieldset[disabled] .btn-google-plus:active,.btn-google-plus.disabled.active,.btn-google-plus[disabled].active,fieldset[disabled] .btn-google-plus.active{background-color:#dd4b39;border-color:rgba(0,0,0,0.2)}
.btn-instagram{color:#fff;background-color:#3f729b;border-color:rgba(0,0,0,0.2)}.btn-instagram:hover,.btn-instagram:focus,.btn-instagram:active,.btn-instagram.active,.open .dropdown-toggle.btn-instagram{color:#fff;background-color:#335d7e;border-color:rgba(0,0,0,0.2)}
.btn-instagram:active,.btn-instagram.active,.open .dropdown-toggle.btn-instagram{background-image:none}
.btn-instagram.disabled,.btn-instagram[disabled],fieldset[disabled] .btn-instagram,.btn-instagram.disabled:hover,.btn-instagram[disabled]:hover,fieldset[disabled] .btn-instagram:hover,.btn-instagram.disabled:focus,.btn-instagram[disabled]:focus,fieldset[disabled] .btn-instagram:focus,.btn-instagram.disabled:active,.btn-instagram[disabled]:active,fieldset[disabled] .btn-instagram:active,.btn-instagram.disabled.active,.btn-instagram[disabled].active,fieldset[disabled] .btn-instagram.active{background-color:#3f729b;border-color:rgba(0,0,0,0.2)}
.btn-linkedin{color:#fff;background-color:#007bb6;border-color:rgba(0,0,0,0.2)}.btn-linkedin:hover,.btn-linkedin:focus,.btn-linkedin:active,.btn-linkedin.active,.open .dropdown-toggle.btn-linkedin{color:#fff;background-color:#005f8d;border-color:rgba(0,0,0,0.2)}
.btn-linkedin:active,.btn-linkedin.active,.open .dropdown-toggle.btn-linkedin{background-image:none}
.btn-linkedin.disabled,.btn-linkedin[disabled],fieldset[disabled] .btn-linkedin,.btn-linkedin.disabled:hover,.btn-linkedin[disabled]:hover,fieldset[disabled] .btn-linkedin:hover,.btn-linkedin.disabled:focus,.btn-linkedin[disabled]:focus,fieldset[disabled] .btn-linkedin:focus,.btn-linkedin.disabled:active,.btn-linkedin[disabled]:active,fieldset[disabled] .btn-linkedin:active,.btn-linkedin.disabled.active,.btn-linkedin[disabled].active,fieldset[disabled] .btn-linkedin.active{background-color:#007bb6;border-color:rgba(0,0,0,0.2)}
.btn-pinterest{color:#fff;background-color:#cb2027;border-color:rgba(0,0,0,0.2)}.btn-pinterest:hover,.btn-pinterest:focus,.btn-pinterest:active,.btn-pinterest.active,.open .dropdown-toggle.btn-pinterest{color:#fff;background-color:#a81a20;border-color:rgba(0,0,0,0.2)}
.btn-pinterest:active,.btn-pinterest.active,.open .dropdown-toggle.btn-pinterest{background-image:none}
.btn-pinterest.disabled,.btn-pinterest[disabled],fieldset[disabled] .btn-pinterest,.btn-pinterest.disabled:hover,.btn-pinterest[disabled]:hover,fieldset[disabled] .btn-pinterest:hover,.btn-pinterest.disabled:focus,.btn-pinterest[disabled]:focus,fieldset[disabled] .btn-pinterest:focus,.btn-pinterest.disabled:active,.btn-pinterest[disabled]:active,fieldset[disabled] .btn-pinterest:active,.btn-pinterest.disabled.active,.btn-pinterest[disabled].active,fieldset[disabled] .btn-pinterest.active{background-color:#cb2027;border-color:rgba(0,0,0,0.2)}
.btn-tumblr{color:#fff;background-color:#2c4762;border-color:rgba(0,0,0,0.2)}.btn-tumblr:hover,.btn-tumblr:focus,.btn-tumblr:active,.btn-tumblr.active,.open .dropdown-toggle.btn-tumblr{color:#fff;background-color:#1f3346;border-color:rgba(0,0,0,0.2)}
.btn-tumblr:active,.btn-tumblr.active,.open .dropdown-toggle.btn-tumblr{background-image:none}
.btn-tumblr.disabled,.btn-tumblr[disabled],fieldset[disabled] .btn-tumblr,.btn-tumblr.disabled:hover,.btn-tumblr[disabled]:hover,fieldset[disabled] .btn-tumblr:hover,.btn-tumblr.disabled:focus,.btn-tumblr[disabled]:focus,fieldset[disabled] .btn-tumblr:focus,.btn-tumblr.disabled:active,.btn-tumblr[disabled]:active,fieldset[disabled] .btn-tumblr:active,.btn-tumblr.disabled.active,.btn-tumblr[disabled].active,fieldset[disabled] .btn-tumblr.active{background-color:#2c4762;border-color:rgba(0,0,0,0.2)}
.btn-twitter{color:#fff;background-color:#55acee;border-color:rgba(0,0,0,0.2)}.btn-twitter:hover,.btn-twitter:focus,.btn-twitter:active,.btn-twitter.active,.open .dropdown-toggle.btn-twitter{color:#fff;background-color:#309aea;border-color:rgba(0,0,0,0.2)}
.btn-twitter:active,.btn-twitter.active,.open .dropdown-toggle.btn-twitter{background-image:none}
.btn-twitter.disabled,.btn-twitter[disabled],fieldset[disabled] .btn-twitter,.btn-twitter.disabled:hover,.btn-twitter[disabled]:hover,fieldset[disabled] .btn-twitter:hover,.btn-twitter.disabled:focus,.btn-twitter[disabled]:focus,fieldset[disabled] .btn-twitter:focus,.btn-twitter.disabled:active,.btn-twitter[disabled]:active,fieldset[disabled] .btn-twitter:active,.btn-twitter.disabled.active,.btn-twitter[disabled].active,fieldset[disabled] .btn-twitter.active{background-color:#55acee;border-color:rgba(0,0,0,0.2)}
.btn-vk{color:#fff;background-color:#587ea3;border-color:rgba(0,0,0,0.2)}.btn-vk:hover,.btn-vk:focus,.btn-vk:active,.btn-vk.active,.open .dropdown-toggle.btn-vk{color:#fff;background-color:#4a6a89;border-color:rgba(0,0,0,0.2)}
.btn-vk:active,.btn-vk.active,.open .dropdown-toggle.btn-vk{background-image:none}
.btn-vk.disabled,.btn-vk[disabled],fieldset[disabled] .btn-vk,.btn-vk.disabled:hover,.btn-vk[disabled]:hover,fieldset[disabled] .btn-vk:hover,.btn-vk.disabled:focus,.btn-vk[disabled]:focus,fieldset[disabled] .btn-vk:focus,.btn-vk.disabled:active,.btn-vk[disabled]:active,fieldset[disabled] .btn-vk:active,.btn-vk.disabled.active,.btn-vk[disabled].active,fieldset[disabled] .btn-vk.active{background-color:#587ea3;border-color:rgba(0,0,0,0.2)}
Loading…
Cancel
Save