├── .env.sample ├── .gitignore ├── .ruby-version ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Procfile ├── Procfile.dev ├── README.md ├── Rakefile ├── app ├── admin │ ├── admin_user.rb │ ├── dashboard.rb │ └── users.rb ├── assets │ ├── images │ │ ├── email │ │ │ ├── facebook.jpg │ │ │ ├── harrys-is-coming.jpg │ │ │ ├── logo.jpg │ │ │ ├── refer.jpg │ │ │ └── twitter.jpg │ │ ├── favicon.ico │ │ ├── home │ │ │ ├── hero.jpg │ │ │ ├── hero@2x.jpg │ │ │ ├── key.png │ │ │ └── key@2x.png │ │ └── refer │ │ │ ├── blade-explain.png │ │ │ ├── blade-explain@2x.png │ │ │ ├── cream-tooltip.jpg │ │ │ ├── cream-tooltip.png │ │ │ ├── cream-tooltip@2x.jpg │ │ │ ├── cream-tooltip@2x.png │ │ │ ├── fb.png │ │ │ ├── fb@2x.png │ │ │ ├── logo.png │ │ │ ├── logo@2x.png │ │ │ ├── mammoth.png │ │ │ ├── mammoth@2x.png │ │ │ ├── progress-bg.png │ │ │ ├── progress-bg@2x.png │ │ │ ├── progress-done.png │ │ │ ├── progress-done@2x.png │ │ │ ├── truman.png │ │ │ ├── truman@2x.png │ │ │ ├── twit.png │ │ │ ├── twit@2x.png │ │ │ ├── winston.png │ │ │ └── winston@2x.png │ ├── javascripts │ │ ├── active_admin.js │ │ ├── application.js │ │ └── users.js │ └── stylesheets │ │ ├── _core.scss │ │ ├── _users.scss │ │ ├── active_admin.css.scss │ │ ├── application.scss │ │ ├── bootstrap.css │ │ └── reset.css ├── controllers │ ├── application_controller.rb │ └── users_controller.rb ├── helpers │ ├── application_helper.rb │ └── users_helper.rb ├── mailers │ ├── .gitkeep │ └── user_mailer.rb ├── models │ ├── .gitkeep │ ├── admin_user.rb │ ├── ip_address.rb │ └── user.rb └── views │ ├── layouts │ └── application.html.erb │ ├── user_mailer │ └── signup_email.html.erb │ └── users │ ├── _campaign_ended.html.erb │ ├── _campaign_ongoing.html.erb │ ├── new.html.erb │ ├── policy.html.erb │ └── refer.html.erb ├── config.ru ├── config ├── application.rb ├── boot.rb ├── database.yml ├── database.yml.sample ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── active_admin.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── devise.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── session_store.rb │ ├── strong_parameters.rb │ └── wrap_parameters.rb ├── locales │ ├── devise.en.yml │ └── en.yml ├── routes.rb ├── secrets.yml └── unicorn.rb ├── db ├── migrate │ ├── 20130126215239_create_users.rb │ ├── 20130127063936_devise_create_admin_users.rb │ ├── 20130127063939_create_admin_notes.rb │ ├── 20130127063940_move_admin_notes_to_comments.rb │ ├── 20130227185712_create_delayed_jobs.rb │ └── 20130312045541_create_ip_addresses.rb ├── schema.rb └── seeds.rb ├── doc └── README_FOR_APP ├── lib ├── assets │ └── .gitkeep └── tasks │ └── prelaunchr.rake ├── log └── .gitkeep ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico └── robots.txt ├── script ├── delayed_job └── rails ├── spec ├── controllers │ └── user_spec.rb ├── integration │ └── user_spec.rb ├── models │ └── user_spec.rb ├── rails_helper.rb └── spec_helper.rb └── vendor ├── assets ├── javascripts │ └── .gitkeep └── stylesheets │ └── .gitkeep └── plugins └── .gitkeep /.env.sample: -------------------------------------------------------------------------------- 1 | DEFAULT_MAILER_HOST="localhost:3000" 2 | SECRET_KEY_BASE="xxxxxxxx-change-me-please-xxxxxxxx" 3 | CAMPAIGN_ENDED=false 4 | MAILER_SENDER=please-change-me-at-config-initializers-devise@example.com -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile ~/.gitignore_global 6 | 7 | *.rbc 8 | capybara-*.html 9 | .rspec 10 | /log 11 | /tmp 12 | /db/*.sqlite3 13 | /public/system 14 | /coverage/ 15 | /spec/tmp 16 | **.orig 17 | rerun.txt 18 | pickle-email-*.html 19 | 20 | # TODO Comment out these rules if you are OK with secrets being uploaded to the repo 21 | config/initializers/secret_token.rb 22 | config/secrets.yml 23 | 24 | ## Environment normalisation: 25 | /.bundle 26 | /vendor/bundle 27 | 28 | # these should all be checked in to normalise the environment: 29 | # Gemfile.lock, .ruby-gemset 30 | 31 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 32 | .rvmrc 33 | # Ignore the default database config 34 | config/database.yml 35 | 36 | # Ignore all logfiles and tempfiles. 37 | /lib/assets/*.csv 38 | 39 | # if using bower-rails ignore default bower_components path bower.json files 40 | /vendor/assets/bower_components 41 | *.bowerrc 42 | bower.json 43 | 44 | /lib/assets/*.csv 45 | .DS_Store 46 | .elasticbeanstalk 47 | 48 | /.env 49 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.5.3 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.5.0 4 | env: 5 | - DEFAULT_MAILER_HOST=localhost:1080 SECRET_KEY_BASE=asecretkeybase CAMPAIGN_ENDED=false MAILER_SENDER=mailer@example.com 6 | services: 7 | - postgresql 8 | addons: 9 | postgresql: "9.4" 10 | before_script: 11 | - psql -c 'create database prelaunchr_test;' -U postgres 12 | script: 13 | - RAILS_ENV=test bundle exec rake db:migrate --trace 14 | - bundle exec rake db:test:prepare 15 | - bundle exec rspec spec/ 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | ruby '2.5.3' 4 | 5 | gem 'activeadmin', '1.0.0' 6 | gem 'delayed_job_active_record', '~> 4.1.2' 7 | gem 'devise' 8 | gem 'pg' 9 | gem 'rails', '~> 4.2' 10 | gem 'unicorn' 11 | 12 | 13 | # Gems used only for assets and not required 14 | # in production environments by default. 15 | group :assets do 16 | gem 'coffee-rails', '~> 4.2.2' 17 | gem 'sass-rails', '~> 5.0.7' 18 | gem 'uglifier' 19 | end 20 | 21 | group :development, :test do 22 | gem 'pry' 23 | gem 'rspec-rails', '3.4.2' 24 | gem 'rspec-mocks', '3.4.1' 25 | gem 'test-unit', '~> 3.0' 26 | gem "dotenv-rails" 27 | end 28 | 29 | # To use ActiveModel has_secure_password 30 | # gem 'bcrypt-ruby', '~> 3.0.0' 31 | 32 | # To use Jbuilder templates for JSON 33 | # gem 'jbuilder' 34 | 35 | # Use unicorn as the app server 36 | # gem 'unicorn' 37 | 38 | # Deploy with Capistrano 39 | # gem 'capistrano' 40 | 41 | group :production do 42 | gem 'rails_12factor' 43 | gem 'rails_serve_static_assets' 44 | end 45 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actionmailer (4.2.11) 5 | actionpack (= 4.2.11) 6 | actionview (= 4.2.11) 7 | activejob (= 4.2.11) 8 | mail (~> 2.5, >= 2.5.4) 9 | rails-dom-testing (~> 1.0, >= 1.0.5) 10 | actionpack (4.2.11) 11 | actionview (= 4.2.11) 12 | activesupport (= 4.2.11) 13 | rack (~> 1.6) 14 | rack-test (~> 0.6.2) 15 | rails-dom-testing (~> 1.0, >= 1.0.5) 16 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 17 | actionview (4.2.11) 18 | activesupport (= 4.2.11) 19 | builder (~> 3.1) 20 | erubis (~> 2.7.0) 21 | rails-dom-testing (~> 1.0, >= 1.0.5) 22 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 23 | activeadmin (1.0.0) 24 | arbre (>= 1.1.1) 25 | bourbon 26 | coffee-rails 27 | formtastic (~> 3.1) 28 | formtastic_i18n 29 | inherited_resources (~> 1.7) 30 | jquery-rails 31 | jquery-ui-rails 32 | kaminari (>= 0.15, < 2.0) 33 | railties (>= 4.2, < 5.2) 34 | ransack (~> 1.3) 35 | sass-rails 36 | sprockets (< 4.1) 37 | activejob (4.2.11) 38 | activesupport (= 4.2.11) 39 | globalid (>= 0.3.0) 40 | activemodel (4.2.11) 41 | activesupport (= 4.2.11) 42 | builder (~> 3.1) 43 | activerecord (4.2.11) 44 | activemodel (= 4.2.11) 45 | activesupport (= 4.2.11) 46 | arel (~> 6.0) 47 | activesupport (4.2.11) 48 | i18n (~> 0.7) 49 | minitest (~> 5.1) 50 | thread_safe (~> 0.3, >= 0.3.4) 51 | tzinfo (~> 1.1) 52 | arbre (1.1.1) 53 | activesupport (>= 3.0.0) 54 | arel (6.0.4) 55 | bcrypt (3.1.11) 56 | bourbon (5.0.0) 57 | sass (~> 3.4) 58 | thor (~> 0.19) 59 | builder (3.2.3) 60 | coderay (1.1.1) 61 | coffee-rails (4.2.2) 62 | coffee-script (>= 2.2.0) 63 | railties (>= 4.0.0) 64 | coffee-script (2.4.1) 65 | coffee-script-source 66 | execjs 67 | coffee-script-source (1.12.2) 68 | concurrent-ruby (1.1.3) 69 | crass (1.0.4) 70 | delayed_job (4.1.4) 71 | activesupport (>= 3.0, < 5.2) 72 | delayed_job_active_record (4.1.2) 73 | activerecord (>= 3.0, < 5.2) 74 | delayed_job (>= 3.0, < 5) 75 | devise (4.4.1) 76 | bcrypt (~> 3.0) 77 | orm_adapter (~> 0.1) 78 | railties (>= 4.1.0, < 5.2) 79 | responders 80 | warden (~> 1.2.3) 81 | diff-lcs (1.3) 82 | dotenv (2.1.0) 83 | dotenv-rails (2.1.0) 84 | dotenv (= 2.1.0) 85 | railties (>= 4.0, < 5.1) 86 | erubis (2.7.0) 87 | execjs (2.7.0) 88 | ffi (1.9.25) 89 | formtastic (3.1.5) 90 | actionpack (>= 3.2.13) 91 | formtastic_i18n (0.6.0) 92 | globalid (0.4.1) 93 | activesupport (>= 4.2.0) 94 | has_scope (0.7.1) 95 | actionpack (>= 4.1, < 5.2) 96 | activesupport (>= 4.1, < 5.2) 97 | i18n (0.9.5) 98 | concurrent-ruby (~> 1.0) 99 | inherited_resources (1.8.0) 100 | actionpack (>= 4.2, <= 5.2) 101 | has_scope (~> 0.6) 102 | railties (>= 4.2, <= 5.2) 103 | responders 104 | jquery-rails (4.3.1) 105 | rails-dom-testing (>= 1, < 3) 106 | railties (>= 4.2.0) 107 | thor (>= 0.14, < 2.0) 108 | jquery-ui-rails (6.0.1) 109 | railties (>= 3.2.16) 110 | json (2.1.0) 111 | kaminari (1.1.1) 112 | activesupport (>= 4.1.0) 113 | kaminari-actionview (= 1.1.1) 114 | kaminari-activerecord (= 1.1.1) 115 | kaminari-core (= 1.1.1) 116 | kaminari-actionview (1.1.1) 117 | actionview 118 | kaminari-core (= 1.1.1) 119 | kaminari-activerecord (1.1.1) 120 | activerecord 121 | kaminari-core (= 1.1.1) 122 | kaminari-core (1.1.1) 123 | kgio (2.10.0) 124 | loofah (2.2.3) 125 | crass (~> 1.0.2) 126 | nokogiri (>= 1.5.9) 127 | mail (2.7.1) 128 | mini_mime (>= 0.1.1) 129 | method_source (0.8.2) 130 | mini_mime (1.0.1) 131 | mini_portile2 (2.3.0) 132 | minitest (5.11.3) 133 | nokogiri (1.8.5) 134 | mini_portile2 (~> 2.3.0) 135 | orm_adapter (0.5.0) 136 | pg (0.18.4) 137 | polyamorous (1.3.3) 138 | activerecord (>= 3.0) 139 | power_assert (0.2.7) 140 | pry (0.10.3) 141 | coderay (~> 1.1.0) 142 | method_source (~> 0.8.1) 143 | slop (~> 3.4) 144 | rack (1.6.11) 145 | rack-test (0.6.3) 146 | rack (>= 1.0) 147 | rails (4.2.11) 148 | actionmailer (= 4.2.11) 149 | actionpack (= 4.2.11) 150 | actionview (= 4.2.11) 151 | activejob (= 4.2.11) 152 | activemodel (= 4.2.11) 153 | activerecord (= 4.2.11) 154 | activesupport (= 4.2.11) 155 | bundler (>= 1.3.0, < 2.0) 156 | railties (= 4.2.11) 157 | sprockets-rails 158 | rails-deprecated_sanitizer (1.0.3) 159 | activesupport (>= 4.2.0.alpha) 160 | rails-dom-testing (1.0.9) 161 | activesupport (>= 4.2.0, < 5.0) 162 | nokogiri (~> 1.6) 163 | rails-deprecated_sanitizer (>= 1.0.1) 164 | rails-html-sanitizer (1.0.4) 165 | loofah (~> 2.2, >= 2.2.2) 166 | rails_12factor (0.0.3) 167 | rails_serve_static_assets 168 | rails_stdout_logging 169 | rails_serve_static_assets (0.0.5) 170 | rails_stdout_logging (0.0.4) 171 | railties (4.2.11) 172 | actionpack (= 4.2.11) 173 | activesupport (= 4.2.11) 174 | rake (>= 0.8.7) 175 | thor (>= 0.18.1, < 2.0) 176 | raindrops (0.16.0) 177 | rake (12.3.2) 178 | ransack (1.8.6) 179 | actionpack (>= 3.0) 180 | activerecord (>= 3.0) 181 | activesupport (>= 3.0) 182 | i18n 183 | polyamorous (~> 1.3.2) 184 | rb-fsevent (0.10.2) 185 | rb-inotify (0.9.10) 186 | ffi (>= 0.5.0, < 2) 187 | responders (2.4.0) 188 | actionpack (>= 4.2.0, < 5.3) 189 | railties (>= 4.2.0, < 5.3) 190 | rspec-core (3.4.4) 191 | rspec-support (~> 3.4.0) 192 | rspec-expectations (3.4.0) 193 | diff-lcs (>= 1.2.0, < 2.0) 194 | rspec-support (~> 3.4.0) 195 | rspec-mocks (3.4.1) 196 | diff-lcs (>= 1.2.0, < 2.0) 197 | rspec-support (~> 3.4.0) 198 | rspec-rails (3.4.2) 199 | actionpack (>= 3.0, < 4.3) 200 | activesupport (>= 3.0, < 4.3) 201 | railties (>= 3.0, < 4.3) 202 | rspec-core (~> 3.4.0) 203 | rspec-expectations (~> 3.4.0) 204 | rspec-mocks (~> 3.4.0) 205 | rspec-support (~> 3.4.0) 206 | rspec-support (3.4.1) 207 | sass (3.5.5) 208 | sass-listen (~> 4.0.0) 209 | sass-listen (4.0.0) 210 | rb-fsevent (~> 0.9, >= 0.9.4) 211 | rb-inotify (~> 0.9, >= 0.9.7) 212 | sass-rails (5.0.7) 213 | railties (>= 4.0.0, < 6) 214 | sass (~> 3.1) 215 | sprockets (>= 2.8, < 4.0) 216 | sprockets-rails (>= 2.0, < 4.0) 217 | tilt (>= 1.1, < 3) 218 | slop (3.6.0) 219 | sprockets (3.7.2) 220 | concurrent-ruby (~> 1.0) 221 | rack (> 1, < 3) 222 | sprockets-rails (3.2.1) 223 | actionpack (>= 4.0) 224 | activesupport (>= 4.0) 225 | sprockets (>= 3.0.0) 226 | test-unit (3.1.7) 227 | power_assert 228 | thor (0.20.3) 229 | thread_safe (0.3.6) 230 | tilt (2.0.8) 231 | tzinfo (1.2.5) 232 | thread_safe (~> 0.1) 233 | uglifier (2.7.2) 234 | execjs (>= 0.3.0) 235 | json (>= 1.8.0) 236 | unicorn (5.0.1) 237 | kgio (~> 2.6) 238 | rack 239 | raindrops (~> 0.7) 240 | warden (1.2.7) 241 | rack (>= 1.0) 242 | 243 | PLATFORMS 244 | ruby 245 | 246 | DEPENDENCIES 247 | activeadmin (= 1.0.0) 248 | coffee-rails (~> 4.2.2) 249 | delayed_job_active_record (~> 4.1.2) 250 | devise 251 | dotenv-rails 252 | pg 253 | pry 254 | rails (~> 4.2) 255 | rails_12factor 256 | rails_serve_static_assets 257 | rspec-mocks (= 3.4.1) 258 | rspec-rails (= 3.4.2) 259 | sass-rails (~> 5.0.7) 260 | test-unit (~> 3.0) 261 | uglifier 262 | unicorn 263 | 264 | RUBY VERSION 265 | ruby 2.5.3p105 266 | 267 | BUNDLED WITH 268 | 1.17.1 269 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2014 Harry's / ADKM Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb 2 | worker: bundle exec rake jobs:work 3 | 4 | -------------------------------------------------------------------------------- /Procfile.dev: -------------------------------------------------------------------------------- 1 | web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb 2 | worker: bundle exec rake jobs:work 3 | mailcatcher: mailcatcher -f -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Prelaunchr 2 | ========== 3 | 4 | ## Archived 5 | 6 | **Warning: This project is unmaintained and has dependencies with known security vulnerabilities.** 7 | 8 | Six years after our own viral pre-launch campaign, and having enabled many other teams to start with a splash, it is time to retire the Prelaunchr project. We have taken the step of archiving Prelaunchr - it has been hard to update vulnerable dependencies of a project that we haven't used since 2013! You are welcome to fork the project, but we recommend updating all dependencies to recent versions and enabling Github's security scans. 9 | 10 | We will not be updating the project or responding to issues or PRs. Good luck with your launches and beyond. 11 | 12 | — Harry's Engineering Team 13 | 14 | 15 | [![Build Status](https://travis-ci.org/harrystech/prelaunchr.svg?branch=master)](https://travis-ci.org/harrystech/prelaunchr) 16 | 17 | ## About 18 | 19 | Originally open sourced over on our [engineering blog](http://engineering.harrys.com/2014/07/21/dont-launch-crickets.html), 20 | and discussed in great detail over at [Tim Ferriss' Blog](http://fourhourworkweek.com/2014/07/21/harrys-prelaunchr-email), 21 | Prelaunchr is a skeleton Rails application for quickly starting a viral 22 | prelaunch campaign for new companies or products. The campaign is conducive to 23 | social sharing and has prize levels based on the number of people each person 24 | refers. By default, we've included our original HTML/CSS for both the site and 25 | email to give you a better idea of how this looked when actually running. 26 | 27 | ## Mechanics 28 | 29 | Prelaunchr has a main mechanic from which everything else is derived: Every 30 | `User` is given a unique `referral_code` which is how the application knows who 31 | referred a signing up user. Based on the amount of referrals a `User` has 32 | brought to the site, they are put into a different "prize group". The groups, 33 | amounts, and prizes are completely up to you to set. 34 | 35 | ## IP Blocking 36 | 37 | By default, we block more than 2 sign-ups from the same IP address. This was 38 | simplistic, but was good enough for us during our campaign. If you want 39 | something more substantial take a look at [Rack::Attack](https://github.com/kickstarter/rack-attack) 40 | 41 | 42 | ## Developer Setup 43 | 44 | Get Ruby 2.5.0 (rbenv), bundle and install: 45 | 46 | ```no-highlight 47 | brew update && brew upgrade ruby-build && rbenv install 2.5.0 48 | ``` 49 | 50 | Clone the repo and enter the folder (commands not shown). 51 | 52 | Install Bundler, Foreman and Mailcatcher then Bundle: 53 | 54 | ```no-highlight 55 | gem install bundler foreman mailcatcher 56 | bundle 57 | ``` 58 | 59 | Copy the local `database.yml` file sample and `.env.sample`: 60 | 61 | ```no-highlight 62 | cp config/database.yml.sample config/database.yml 63 | cp .env.sample .env 64 | ``` 65 | 66 | Update your newly created .env file with the needed configuration 67 | DEFAULT\_MAILER\_HOST: sets the action mailer default host as seen in 68 | config/environment/.rb. You will minimally need this in production. 69 | SECRET\_KEY\_BASE: sets a secret key to be used by config/initializers/devise.rb 70 | 71 | Setup your local database: 72 | 73 | ```no-highlight 74 | bundle exec rake db:create 75 | bundle exec rake db:migrate 76 | ``` 77 | 78 | Start local server and mail worker: 79 | 80 | ```no-highlight 81 | foreman start -f Procfile.dev 82 | ``` 83 | 84 | View your website at the port default `http://localhost:5000/`. 85 | View sent mails at `http://localhost:1080/`. 86 | 87 | ### To create an admin account 88 | 89 | In Rails console, run this command. Be careful to not use the example admin user 90 | for security reasons. Password confirmation should match password. 91 | 92 | `AdminUser.create!(:email => 'admin@example.com', :password => 'password', :password_confirmation => 'passwordconfirmaiton')` 93 | 94 | You can run this locally in a Rails console for development testing. 95 | 96 | If you are deployed to Heroku, you would run it there. 97 | 98 | ## Teardown 99 | 100 | When your prelaunch campaign comes to an end we've included a helpful `rake` 101 | task to help you compile the list of winners into CSV's containing the email 102 | addresses and the amount of referrals the user had. 103 | 104 | * Run `bundle exec rake prelaunchr:create_winner_csvs` and the app will export 105 | CSV's in `/lib/assets` corresponding to each prize group. 106 | 107 | ## Configuration 108 | 109 | * Set the different prize levels on the `User::REFERRAL_STEPS` constant inside 110 | `/app/models/user.rb` 111 | * The `config.ended` setting in `/config/application.rb` decides whether the 112 | prelaunch campaign has ended or not (e.g. Active/Inactive). We've included this 113 | option so you can quickly close the application and direct users to your newly 114 | launched site. 115 | 116 | ## License 117 | 118 | The code, documentation, non-branded copy and configuration are released under 119 | the MIT license. Any branded assets are included only to illustrate and inspire. 120 | 121 | Please change the artwork to use your own brand! Harry's does not give 122 | you permission to use our brand or trademarks in your own marketing. 123 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # Add your own tasks in files placed in lib/tasks ending in .rake, 3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 4 | 5 | require File.expand_path('../config/application', __FILE__) 6 | 7 | Prelaunchr::Application.load_tasks 8 | -------------------------------------------------------------------------------- /app/admin/admin_user.rb: -------------------------------------------------------------------------------- 1 | ActiveAdmin.register AdminUser do 2 | index do 3 | column :email 4 | column :current_sign_in_at 5 | column :last_sign_in_at 6 | column :sign_in_count 7 | actions 8 | end 9 | 10 | filter :email 11 | 12 | form do |f| 13 | f.inputs "Admin Details" do 14 | f.input :email 15 | f.input :password 16 | f.input :password_confirmation 17 | end 18 | f.actions 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/admin/dashboard.rb: -------------------------------------------------------------------------------- 1 | ActiveAdmin.register_page "Dashboard" do 2 | 3 | menu :priority => 1, :label => proc{ I18n.t("active_admin.dashboard") } 4 | 5 | content :title => proc{ I18n.t("active_admin.dashboard") } do 6 | div :class => "blank_slate_container", :id => "dashboard_default_message" do 7 | span :class => "blank_slate" do 8 | span I18n.t("active_admin.dashboard_welcome.welcome") 9 | small I18n.t("active_admin.dashboard_welcome.call_to_action") 10 | end 11 | end 12 | 13 | # Here is an example of a simple dashboard with columns and panels. 14 | # 15 | # columns do 16 | # column do 17 | # panel "Recent Posts" do 18 | # ul do 19 | # Post.recent(5).map do |post| 20 | # li link_to(post.title, admin_post_path(post)) 21 | # end 22 | # end 23 | # end 24 | # end 25 | 26 | # column do 27 | # panel "Info" do 28 | # para "Welcome to ActiveAdmin." 29 | # end 30 | # end 31 | # end 32 | end # content 33 | end 34 | -------------------------------------------------------------------------------- /app/admin/users.rb: -------------------------------------------------------------------------------- 1 | # Export to CSV with the referrer_id 2 | ActiveAdmin.register User do 3 | csv do 4 | column :id 5 | column :email 6 | column :referral_code 7 | column :referrer_id 8 | column :created_at 9 | column :updated_at 10 | end 11 | 12 | actions :index, :show 13 | 14 | end 15 | -------------------------------------------------------------------------------- /app/assets/images/email/facebook.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/email/facebook.jpg -------------------------------------------------------------------------------- /app/assets/images/email/harrys-is-coming.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/email/harrys-is-coming.jpg -------------------------------------------------------------------------------- /app/assets/images/email/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/email/logo.jpg -------------------------------------------------------------------------------- /app/assets/images/email/refer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/email/refer.jpg -------------------------------------------------------------------------------- /app/assets/images/email/twitter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/email/twitter.jpg -------------------------------------------------------------------------------- /app/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/favicon.ico -------------------------------------------------------------------------------- /app/assets/images/home/hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/home/hero.jpg -------------------------------------------------------------------------------- /app/assets/images/home/hero@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/home/hero@2x.jpg -------------------------------------------------------------------------------- /app/assets/images/home/key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/home/key.png -------------------------------------------------------------------------------- /app/assets/images/home/key@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/home/key@2x.png -------------------------------------------------------------------------------- /app/assets/images/refer/blade-explain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/blade-explain.png -------------------------------------------------------------------------------- /app/assets/images/refer/blade-explain@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/blade-explain@2x.png -------------------------------------------------------------------------------- /app/assets/images/refer/cream-tooltip.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/cream-tooltip.jpg -------------------------------------------------------------------------------- /app/assets/images/refer/cream-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/cream-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/refer/cream-tooltip@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/cream-tooltip@2x.jpg -------------------------------------------------------------------------------- /app/assets/images/refer/cream-tooltip@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/cream-tooltip@2x.png -------------------------------------------------------------------------------- /app/assets/images/refer/fb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/fb.png -------------------------------------------------------------------------------- /app/assets/images/refer/fb@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/fb@2x.png -------------------------------------------------------------------------------- /app/assets/images/refer/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/logo.png -------------------------------------------------------------------------------- /app/assets/images/refer/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/logo@2x.png -------------------------------------------------------------------------------- /app/assets/images/refer/mammoth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/mammoth.png -------------------------------------------------------------------------------- /app/assets/images/refer/mammoth@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/mammoth@2x.png -------------------------------------------------------------------------------- /app/assets/images/refer/progress-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/progress-bg.png -------------------------------------------------------------------------------- /app/assets/images/refer/progress-bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/progress-bg@2x.png -------------------------------------------------------------------------------- /app/assets/images/refer/progress-done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/progress-done.png -------------------------------------------------------------------------------- /app/assets/images/refer/progress-done@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/progress-done@2x.png -------------------------------------------------------------------------------- /app/assets/images/refer/truman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/truman.png -------------------------------------------------------------------------------- /app/assets/images/refer/truman@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/truman@2x.png -------------------------------------------------------------------------------- /app/assets/images/refer/twit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/twit.png -------------------------------------------------------------------------------- /app/assets/images/refer/twit@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/twit@2x.png -------------------------------------------------------------------------------- /app/assets/images/refer/winston.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/winston.png -------------------------------------------------------------------------------- /app/assets/images/refer/winston@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/assets/images/refer/winston@2x.png -------------------------------------------------------------------------------- /app/assets/javascripts/active_admin.js: -------------------------------------------------------------------------------- 1 | //= require active_admin/base 2 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // the compiled file. 9 | // 10 | // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD 11 | // GO AFTER THE REQUIRES BELOW. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require_tree . 16 | -------------------------------------------------------------------------------- /app/assets/javascripts/users.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | var myVideo = document.getElementById('video'); 3 | console.log(myVideo); 4 | if(myVideo){ 5 | if (typeof myVideo.loop == 'boolean') { // loop supported 6 | myVideo.loop = true; 7 | } else { // loop property not supported 8 | myVideo.on('ended', function () { 9 | this.currentTime = 0; 10 | this.play(); 11 | }, false); 12 | } 13 | } 14 | }) -------------------------------------------------------------------------------- /app/assets/stylesheets/_core.scss: -------------------------------------------------------------------------------- 1 | form{ 2 | margin-bottom: 0; 3 | } 4 | 5 | .clearfix:before, 6 | .clearfix:after { 7 | content: " "; /* 1 */ 8 | display: table; /* 2 */ 9 | } 10 | 11 | .clearfix:after { 12 | clear: both; 13 | } 14 | 15 | 16 | /* Home */ 17 | 18 | html.home{ 19 | background: #EDECEA; 20 | } 21 | 22 | #home{ 23 | padding-bottom: 50px; 24 | } 25 | 26 | #home .page-content{ 27 | width: 1400px; 28 | margin: 0 auto; 29 | } 30 | 31 | #home .hero{ 32 | width: 100%; 33 | height: 446px; 34 | background: image-url("home/hero.jpg") center center no-repeat; 35 | background-size: 1600px 446px; 36 | overflow: hidden; 37 | position: relative; 38 | letter-spacing: .09em; 39 | margin: 0 auto; 40 | } 41 | 42 | #home .hero .large{ 43 | font-weight: bold; 44 | font-size: 40px; 45 | color: #074E6F; 46 | text-transform: uppercase; 47 | margin: 85px 0 0 136px; 48 | position: relative; 49 | z-index: 2; 50 | letter-spacing: 2px; 51 | } 52 | 53 | #home .hero .small{ 54 | font-weight: bold; 55 | color: #955F5D; 56 | font-size: 18px; 57 | margin: 10px 0 0 115px; 58 | line-height: 30px; 59 | position: relative; 60 | z-index: 2; 61 | text-transform: uppercase; 62 | text-align: center; 63 | width: 375px; 64 | letter-spacing: 1px; 65 | } 66 | 67 | #home .form-wrap{ 68 | height: 80px; 69 | width: 411px; 70 | margin: 23px auto 0; 71 | } 72 | 73 | #home .form-wrap .key{ 74 | width: 46px; 75 | height: 19px; 76 | margin: 0 auto; 77 | background: image-url("home/key.png") 0 0 no-repeat; 78 | background-size: 46px 19px; 79 | } 80 | 81 | #home .form-wrap .byline{ 82 | font-size: 15px; 83 | text-transform: uppercase; 84 | color: #074E6F; 85 | text-align: center; 86 | font-weight: bold; 87 | letter-spacing: 2px; 88 | margin: 15px 0; 89 | } 90 | 91 | #home .form-wrap .email{ 92 | float: left; 93 | font-size: 13px; 94 | padding: 8px 0 8px 10px; 95 | border: 1px solid #074E6F; 96 | width: 256px; 97 | outline: none; 98 | letter-spacing: 1px; 99 | } 100 | 101 | #home .form-wrap .submit{ 102 | width: 143px; 103 | background: #074E6F; 104 | float: left; 105 | height: 37px; 106 | line-height: 37px; 107 | color: #FFF; 108 | font-size: 13px; 109 | text-transform: uppercase; 110 | border: 0; 111 | letter-spacing: 2px; 112 | } 113 | 114 | #home .form-wrap.ended{ 115 | width: 500px; 116 | margin-top: 27px; 117 | } 118 | 119 | #home .form-wrap.ended .thanks{ 120 | text-transform: uppercase; 121 | font-weight: 500; 122 | color: #074e6f; 123 | text-align: center; 124 | font-size: 22px; 125 | letter-spacing: 1px; 126 | line-height: 36px; 127 | margin-bottom: 14px; 128 | } 129 | 130 | #home .form-wrap.ended .compiling{ 131 | text-transform: uppercase; 132 | letter-spacing: 1px; 133 | font-size: 14px; 134 | line-height: 30px; 135 | color: #555556; 136 | text-align: center; 137 | font-weight: 500; 138 | margin-bottom: 24px; 139 | } 140 | 141 | #home .form-wrap.ended .go-to{ 142 | font-style: italic; 143 | font-size: 16px; 144 | letter-spacing: 1px; 145 | text-align: center; 146 | margin-bottom: 25px; 147 | } 148 | 149 | #home .form-wrap.ended .go-to a{ 150 | color: #074e6f; 151 | font-size: 19px; 152 | font-weight:bold; 153 | text-transform: uppercase; 154 | font-style: normal; 155 | text-decoration: none; 156 | } 157 | 158 | #home .form-wrap.ended .go-to a:hover{ 159 | color: #955F5D; 160 | } 161 | 162 | #home .form-wrap.ended .again{ 163 | font-weight: 500; 164 | color: #555556; 165 | text-transform: uppercase; 166 | font-size: 14px; 167 | text-align: center; 168 | } 169 | 170 | #home .form-wrap.ended{ 171 | width: 500px; 172 | margin-top: 27px; 173 | } 174 | 175 | #home .form-wrap.ended .thanks{ 176 | text-transform: uppercase; 177 | font-weight: 500; 178 | color: #074e6f; 179 | text-align: center; 180 | font-size: 22px; 181 | letter-spacing: 1px; 182 | line-height: 36px; 183 | margin-bottom: 14px; 184 | } 185 | 186 | #home .form-wrap.ended .compiling{ 187 | text-transform: uppercase; 188 | letter-spacing: 1px; 189 | font-size: 14px; 190 | line-height: 30px; 191 | color: #555556; 192 | text-align: center; 193 | font-weight: 500; 194 | margin-bottom: 24px; 195 | } 196 | 197 | #home .form-wrap.ended .go-to{ 198 | font-style: italic; 199 | font-size: 16px; 200 | letter-spacing: 1px; 201 | text-align: center; 202 | margin-bottom: 25px; 203 | } 204 | 205 | #home .form-wrap.ended .go-to a{ 206 | color: #074e6f; 207 | font-size: 19px; 208 | font-weight:bold; 209 | text-transform: uppercase; 210 | font-style: normal; 211 | text-decoration: none; 212 | } 213 | 214 | #home .form-wrap.ended .go-to a:hover{ 215 | color: #955F5D; 216 | } 217 | 218 | #home .form-wrap.ended .again{ 219 | font-weight: 500; 220 | color: #555556; 221 | text-transform: uppercase; 222 | font-size: 14px; 223 | text-align: center; 224 | } 225 | 226 | 227 | 228 | /* Refer a friend page */ 229 | 230 | .wf-loading .brandon{ 231 | visibility: hidden; 232 | } 233 | 234 | .brandon{ 235 | font-family: "Brandon Grotesque", "brandon-grotesque", "HelveticaNeue", "Helvetica", "Arial", sans-serif; 236 | } 237 | 238 | #refer{ 239 | background: #FFF; 240 | } 241 | 242 | #refer .page-content{ 243 | width: 1120px; 244 | margin: 0 auto; 245 | z-index: 0; 246 | position: relative; 247 | } 248 | 249 | #refer .header{ 250 | height: 50px; 251 | background: #074E6F; 252 | width: 100%; 253 | overflow:hidden; 254 | z-index: 1; 255 | position: relative; 256 | } 257 | 258 | #refer .header .content{ 259 | width: 315px; 260 | margin: 0 auto; 261 | } 262 | 263 | #refer .header p{ 264 | float: left; 265 | color: #FFF; 266 | font-weight: 500; 267 | text-transform: uppercase; 268 | line-height: 50px; 269 | } 270 | 271 | #refer .header .byline{ 272 | font-size: 14px; 273 | } 274 | 275 | #refer .header .logo{ 276 | font-weight: normal; 277 | font-size: 0; 278 | line-height: 0; 279 | margin-right: 14px; 280 | letter-spacing: 1px; 281 | background: image-url("refer/logo.png") 0 0 no-repeat; 282 | background-size: 106px 19px; 283 | height: 19px; 284 | width: 106px; 285 | margin-top: 15px; 286 | } 287 | 288 | #refer .hero{ 289 | background: #F1F0EE; 290 | height: 391px; 291 | overflow: hidden; 292 | } 293 | 294 | #refer .mammoth{ 295 | width: 612px; 296 | height: 394px; 297 | background: image-url("refer/mammoth.png") 0 0 no-repeat; 298 | background-size: 610px 394px; 299 | overflow: hidden; 300 | margin-top: 15px; 301 | float: left; 302 | } 303 | 304 | #refer .mammoth .need{ 305 | color: #FFF; 306 | font-size: 38px; 307 | text-transform: uppercase; 308 | font-weight: bold; 309 | margin: 102px 0 0 72px; 310 | display: block; 311 | line-height: 56px; 312 | letter-spacing: 4px; 313 | text-align: center; 314 | width: 284px; 315 | } 316 | 317 | #refer .share-wrap{ 318 | float: right; 319 | width: 400px; 320 | text-align: center; 321 | text-transform: uppercase; 322 | letter-spacing: .13em; 323 | margin-right: 60px; 324 | } 325 | 326 | #refer .share-wrap .why{ 327 | font-size: 16px; 328 | color: #555556; 329 | font-weight: bold; 330 | margin-top: 42px; 331 | } 332 | 333 | #refer .share-wrap .title{ 334 | font-size: 30px; 335 | line-height: 40px; 336 | text-transform: uppercase; 337 | font-weight: bold; 338 | color: #074E6F; 339 | margin-top: 18px; 340 | letter-spacing: .13em; 341 | } 342 | 343 | #refer .share-wrap .subtitle{ 344 | color: #565656; 345 | font-size: 14px; 346 | text-transform: none; 347 | margin-top: 15px; 348 | } 349 | 350 | #refer .share-wrap .copy-link{ 351 | border: 1px solid #D1D0C7; 352 | background: #FFF; 353 | padding: 10px 0; 354 | width: 320px; 355 | text-transform: none; 356 | font-size: 11px; 357 | margin: 20px auto 0; 358 | } 359 | 360 | #refer .share-wrap .social-links{ 361 | width: 120px; 362 | height: 37px; 363 | margin: 25px auto 0; 364 | text-align: center; 365 | } 366 | 367 | #refer .share-wrap .social-links a, #refer .share-wrap .social-links div{ 368 | display: inline-block; 369 | } 370 | 371 | #refer .share-wrap .social-links .fb{ 372 | background: image-url("refer/fb.png"); 373 | background-size: 27px 27px; 374 | width: 27px; 375 | height: 27px; 376 | } 377 | 378 | #refer .share-wrap .social-links .twit{ 379 | background: image-url("refer/twit.png"); 380 | background-size: 27px 27px; 381 | width: 27px; 382 | height: 27px; 383 | } 384 | 385 | #refer .share-wrap .social-links .sep{ 386 | height: 28px; 387 | width: 1px; 388 | background: #BAB9BA; 389 | margin: 0 20px; 390 | } 391 | 392 | #refer .prizes{ 393 | width: 100%; 394 | background: #FFF; 395 | border-top: 1px solid #D2D1C8; 396 | padding-bottom: 100px; 397 | } 398 | 399 | #refer .prizes .page-content{ 400 | position: relative; 401 | } 402 | 403 | #refer .prizes .callout{ 404 | text-align: center; 405 | font-size: 16px; 406 | color: #074E6F; 407 | font-weight: bold; 408 | margin: 20px 0 0 0; 409 | letter-spacing: 2px; 410 | text-transform: uppercase; 411 | } 412 | 413 | #refer .prizes .progress{ 414 | height: 4px; 415 | width: 100%; 416 | background: image-url("refer/progress-bg.png") 0 0 repeat-x; 417 | background-size: 1px 4px; 418 | position: absolute; 419 | top: 100px; 420 | z-index: 0; 421 | } 422 | 423 | #refer .prizes .progress .mover{ 424 | position: absolute; 425 | top: 0; 426 | left: 0; 427 | } 428 | 429 | #refer .prizes .progress .mover .bar{ 430 | background: image-url("refer/progress-done.png") 0 0 repeat-x; 431 | width: 165px; 432 | height: 4px; 433 | -webkit-background-size: 1px 4px; 434 | background-size: 1px 4px; 435 | } 436 | 437 | #refer .prize-two .progress .mover .bar{ width: 312px; } 438 | #refer .prize-three .progress .mover .bar{ width: 566px; } 439 | #refer .prize-four .progress .mover .bar{ width: 820px; } 440 | #refer .prize-five .progress .mover .bar{ width: 1120px; } 441 | 442 | 443 | #refer .prizes ul.products{ 444 | height: 105px; 445 | width: 100%; 446 | margin-top: 30px; 447 | position: relative; 448 | z-index: 1; 449 | } 450 | 451 | #refer .prizes .products li.title{ 452 | height: 100%; 453 | text-transform: uppercase; 454 | color: #074E6F; 455 | font-weight: 500; 456 | font-size: 14px; 457 | float: left; 458 | margin-right: 68px; 459 | width: 165px; 460 | font-weight: bold; 461 | } 462 | 463 | #refer .prizes .products li.title .friends{ 464 | margin-top: 7px; 465 | letter-spacing: 2px; 466 | } 467 | 468 | #refer .prizes .products li.title .rewards{ 469 | margin-top: 45px; 470 | letter-spacing: 2px; 471 | } 472 | 473 | #refer .prizes .products li.product{ 474 | height: 100%; 475 | color: #074E6F; 476 | width: 125px; 477 | margin-right: 129px; 478 | float: left; 479 | position: relative; 480 | } 481 | 482 | #refer .prizes .products li.product .circle{ 483 | height: 30px; 484 | width: 30px; 485 | border-radius: 15px; 486 | border: 1px solid #DAD9D2; 487 | text-align: center; 488 | line-height: 30px; 489 | color: #868282; 490 | overflow: hidden; 491 | background: #F1F0EE; 492 | font-weight: bold; 493 | margin: 0 auto; 494 | } 495 | 496 | #refer .prizes .products li.product .sep{ 497 | width: 12px; 498 | height: 2px; 499 | background: transparent; 500 | margin: 13px auto 13px; 501 | } 502 | 503 | #refer .prizes .products li.product .caret{ 504 | width: 100%; 505 | height: 10px; 506 | background: image-url("refer/caret.png") center center no-repeat; 507 | background-size: 16px 10px; 508 | position: absolute; 509 | bottom: -15px; 510 | } 511 | 512 | #refer .prizes .products li.product.selected .caret{ 513 | background: image-url("refer/caret-selected.png") center center no-repeat; 514 | background-size: 16px 10px; 515 | } 516 | 517 | #refer .prizes .products li.product p{ 518 | text-align: center; 519 | font-weight: 500; 520 | } 521 | 522 | #refer .prizes .products li.product:last-child{ 523 | margin-right: 0; 524 | } 525 | 526 | #refer .prizes .products li.product.last{ 527 | margin-right: 0; 528 | } 529 | 530 | #refer .prizes .products li.product.selected .circle{ 531 | background: #995F5E !important; 532 | border-color: #995F5E !important; 533 | color: #FFF !important; 534 | } 535 | 536 | 537 | #refer .prizes .products li.product.selected p{ 538 | color: #995F5E !important; 539 | } 540 | 541 | #refer .prizes .products li.product:hover .circle{ 542 | color: #FFF; 543 | background: #074E6F; 544 | border-color: #074E6F; 545 | } 546 | 547 | #refer .prizes .products li.product .tooltip{ 548 | height: 254px; 549 | width: 190px; 550 | border: 4px solid #074E6F; 551 | position: absolute; 552 | top: -285px; 553 | left: -32px; 554 | display: none; 555 | background: #FFF; 556 | } 557 | 558 | #refer .prizes .products li.product .tooltip img{ 559 | margin: 0 auto; 560 | display: block; 561 | } 562 | 563 | #refer .prizes .products li.product:hover .tooltip{ 564 | display: block; 565 | } 566 | 567 | #refer .prizes p.place{ 568 | text-align: center; 569 | font-size: 16px; 570 | color: #074E6F; 571 | width: 100%; 572 | font-weight: 500; 573 | margin-top: 30px; 574 | } 575 | 576 | #refer .prizes p.place span{ 577 | color: #995F5E; 578 | } 579 | 580 | #refer .prizes p.check{ 581 | text-align: center; 582 | font-size: 16px; 583 | color: #858181; 584 | width: 100%; 585 | font-weight: 500; 586 | margin-top: 5px; 587 | } 588 | 589 | #refer .prizes p.ship{ 590 | text-align: center; 591 | font-size: 16px; 592 | color: #858181; 593 | width: 100%; 594 | font-weight: 500; 595 | margin-top: 50px; 596 | } 597 | 598 | #refer .prizes a.policy{ 599 | text-align: center; 600 | font-size: 12px; 601 | color: #CCCCCC; 602 | width: 100%; 603 | font-weight: 500; 604 | margin-top: 5px; 605 | display: block; 606 | text-decoration: none; 607 | } 608 | 609 | 610 | 611 | /* 612 | RETINA 613 | */ 614 | @media only screen and (min--moz-device-pixel-ratio: 2), 615 | only screen and (-o-min-device-pixel-ratio: 2/1), 616 | only screen and (-webkit-min-device-pixel-ratio: 2), 617 | only screen and (min-device-pixel-ratio: 2) { 618 | #home .hero{ 619 | background: image-url("home/hero@2x.jpg") center center no-repeat; 620 | background-size: 1600px 446px; 621 | } 622 | 623 | #home .form-wrap .key{ 624 | background: image-url("home/key@2x.png") 0 0 no-repeat; 625 | background-size: 46px 19px; 626 | } 627 | 628 | #refer .header .logo{ 629 | background: image-url("refer/logo@2x.png") 0 0 no-repeat; 630 | background-size: 106px 19px; 631 | } 632 | 633 | #refer .mammoth{ 634 | background: image-url("refer/mammoth@2x.png") 0 0 no-repeat; 635 | background-size: 610px 394px; 636 | } 637 | 638 | #refer .share-wrap .social-links .fb{ 639 | background: image-url("refer/fb@2x.png"); 640 | background-size: 27px 27px; 641 | } 642 | 643 | #refer .share-wrap .social-links .twit{ 644 | background: image-url("refer/twit@2x.png"); 645 | background-size: 27px 27px; 646 | } 647 | 648 | #refer .prizes .progress{ 649 | background: image-url("refer/progress-bg@2x.png") 0 0 repeat-x; 650 | background-size: 1px 4px; 651 | } 652 | 653 | #refer .prizes .progress .mover .bar{ 654 | background: image-url("refer/progress-done@2x.png") 0 0 repeat-x; 655 | background-size: 1px 4px; 656 | } 657 | 658 | #refer .prizes .progress .mover .razor{ 659 | background: image-url("refer/razor@2x.png") 0 0 no-repeat; 660 | background-size: 82px 23px; 661 | } 662 | 663 | #refer .prizes .products li.product .caret{ 664 | background: image-url("refer/caret@2x.png") center center no-repeat; 665 | background-size: 16px 10px; 666 | } 667 | 668 | #refer .prizes .products li.product.selected .caret{ 669 | background: image-url("refer/caret-selected@2x.png") center center no-repeat; 670 | background-size: 16px 10px; 671 | } 672 | } 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | -------------------------------------------------------------------------------- /app/assets/stylesheets/_users.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the Users controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/active_admin.css.scss: -------------------------------------------------------------------------------- 1 | // SASS variable overrides must be declared before loading up Active Admin's styles. 2 | // 3 | // To view the variables that Active Admin provides, take a look at 4 | // `app/assets/stylesheets/active_admin/mixins/_variables.css.scss` in the 5 | // Active Admin source. 6 | // 7 | // For example, to change the sidebar width: 8 | // $sidebar-width: 242px; 9 | 10 | // Active Admin's got SASS! 11 | @import "active_admin/mixins"; 12 | @import "active_admin/base"; 13 | 14 | // Overriding any non-variable SASS must be done after the fact. 15 | // For example, to change the default status-tag color: 16 | // 17 | // body.active_admin { 18 | // .status_tag { background: #6090DB; } 19 | // } 20 | // 21 | // Notice that Active Admin CSS rules are nested within a 22 | // 'body.active_admin' selector to prevent conflicts with 23 | // other pages in the app. It is best to wrap your changes in a 24 | // namespace so they are properly recognized. You have options 25 | // if you e.g. want different styles for different namespaces: 26 | // 27 | // .active_admin applies to any Active Admin namespace 28 | // .admin_namespace applies to the admin namespace (eg: /admin) 29 | // .other_namespace applies to a custom namespace named other (eg: /other) 30 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @import "reset"; 2 | @import "bootstrap"; 3 | @import "core"; 4 | @import "users"; 5 | -------------------------------------------------------------------------------- /app/assets/stylesheets/bootstrap.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v2.0.4 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */ 10 | .clearfix { 11 | *zoom: 1; 12 | } 13 | .clearfix:before, 14 | .clearfix:after { 15 | display: table; 16 | content: ""; 17 | } 18 | .clearfix:after { 19 | clear: both; 20 | } 21 | .hide-text { 22 | font: 0/0 a; 23 | color: transparent; 24 | text-shadow: none; 25 | background-color: transparent; 26 | border: 0; 27 | } 28 | .input-block-level { 29 | display: block; 30 | width: 100%; 31 | min-height: 28px; 32 | -webkit-box-sizing: border-box; 33 | -moz-box-sizing: border-box; 34 | -ms-box-sizing: border-box; 35 | box-sizing: border-box; 36 | } 37 | form { 38 | margin: 0 0 18px; 39 | } 40 | fieldset { 41 | padding: 0; 42 | margin: 0; 43 | border: 0; 44 | } 45 | legend { 46 | display: block; 47 | width: 100%; 48 | padding: 0; 49 | margin-bottom: 27px; 50 | font-size: 19.5px; 51 | line-height: 36px; 52 | color: #333333; 53 | border: 0; 54 | border-bottom: 1px solid #e5e5e5; 55 | } 56 | legend small { 57 | font-size: 13.5px; 58 | color: #999999; 59 | } 60 | label, 61 | input, 62 | button, 63 | select, 64 | textarea { 65 | font-size: 13px; 66 | font-weight: normal; 67 | line-height: 18px; 68 | } 69 | input, 70 | button, 71 | select, 72 | textarea { 73 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 74 | } 75 | label { 76 | display: block; 77 | margin-bottom: 5px; 78 | } 79 | select, 80 | textarea, 81 | input[type="text"], 82 | input[type="password"], 83 | input[type="datetime"], 84 | input[type="datetime-local"], 85 | input[type="date"], 86 | input[type="month"], 87 | input[type="time"], 88 | input[type="week"], 89 | input[type="number"], 90 | input[type="email"], 91 | input[type="url"], 92 | input[type="search"], 93 | input[type="tel"], 94 | input[type="color"], 95 | .uneditable-input { 96 | display: inline-block; 97 | height: 18px; 98 | padding: 4px; 99 | margin-bottom: 9px; 100 | font-size: 13px; 101 | line-height: 18px; 102 | color: #555555; 103 | } 104 | input, 105 | textarea { 106 | width: 210px; 107 | } 108 | textarea { 109 | height: auto; 110 | } 111 | textarea, 112 | input[type="text"], 113 | input[type="password"], 114 | input[type="datetime"], 115 | input[type="datetime-local"], 116 | input[type="date"], 117 | input[type="month"], 118 | input[type="time"], 119 | input[type="week"], 120 | input[type="number"], 121 | input[type="email"], 122 | input[type="url"], 123 | input[type="search"], 124 | input[type="tel"], 125 | input[type="color"], 126 | .uneditable-input { 127 | background-color: #ffffff; 128 | border: 1px solid #cccccc; 129 | -webkit-border-radius: 3px; 130 | -moz-border-radius: 3px; 131 | border-radius: 3px; 132 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 133 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 134 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 135 | -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; 136 | -moz-transition: border linear 0.2s, box-shadow linear 0.2s; 137 | -ms-transition: border linear 0.2s, box-shadow linear 0.2s; 138 | -o-transition: border linear 0.2s, box-shadow linear 0.2s; 139 | transition: border linear 0.2s, box-shadow linear 0.2s; 140 | } 141 | textarea:focus, 142 | input[type="text"]:focus, 143 | input[type="password"]:focus, 144 | input[type="datetime"]:focus, 145 | input[type="datetime-local"]:focus, 146 | input[type="date"]:focus, 147 | input[type="month"]:focus, 148 | input[type="time"]:focus, 149 | input[type="week"]:focus, 150 | input[type="number"]:focus, 151 | input[type="email"]:focus, 152 | input[type="url"]:focus, 153 | input[type="search"]:focus, 154 | input[type="tel"]:focus, 155 | input[type="color"]:focus, 156 | .uneditable-input:focus { 157 | border-color: rgba(82, 168, 236, 0.8); 158 | outline: 0; 159 | outline: thin dotted \9; 160 | /* IE6-9 */ 161 | 162 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6); 163 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6); 164 | box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6); 165 | } 166 | input[type="radio"], 167 | input[type="checkbox"] { 168 | margin: 3px 0; 169 | *margin-top: 0; 170 | /* IE7 */ 171 | 172 | line-height: normal; 173 | cursor: pointer; 174 | } 175 | input[type="submit"], 176 | input[type="reset"], 177 | input[type="button"], 178 | input[type="radio"], 179 | input[type="checkbox"] { 180 | width: auto; 181 | } 182 | .uneditable-textarea { 183 | width: auto; 184 | height: auto; 185 | } 186 | select, 187 | input[type="file"] { 188 | height: 28px; 189 | /* In IE7, the height of the select element cannot be changed by height, only font-size */ 190 | 191 | *margin-top: 4px; 192 | /* For IE7, add top margin to align select with labels */ 193 | 194 | line-height: 28px; 195 | } 196 | select { 197 | width: 220px; 198 | border: 1px solid #bbb; 199 | } 200 | select[multiple], 201 | select[size] { 202 | height: auto; 203 | } 204 | select:focus, 205 | input[type="file"]:focus, 206 | input[type="radio"]:focus, 207 | input[type="checkbox"]:focus { 208 | outline: thin dotted #333; 209 | outline: 5px auto -webkit-focus-ring-color; 210 | outline-offset: -2px; 211 | } 212 | .radio, 213 | .checkbox { 214 | min-height: 18px; 215 | padding-left: 18px; 216 | } 217 | .radio input[type="radio"], 218 | .checkbox input[type="checkbox"] { 219 | float: left; 220 | margin-left: -18px; 221 | } 222 | .controls > .radio:first-child, 223 | .controls > .checkbox:first-child { 224 | padding-top: 5px; 225 | } 226 | .radio.inline, 227 | .checkbox.inline { 228 | display: inline-block; 229 | padding-top: 5px; 230 | margin-bottom: 0; 231 | vertical-align: middle; 232 | } 233 | .radio.inline + .radio.inline, 234 | .checkbox.inline + .checkbox.inline { 235 | margin-left: 10px; 236 | } 237 | .input-mini { 238 | width: 60px; 239 | } 240 | .input-small { 241 | width: 90px; 242 | } 243 | .input-medium { 244 | width: 150px; 245 | } 246 | .input-large { 247 | width: 210px; 248 | } 249 | .input-xlarge { 250 | width: 270px; 251 | } 252 | .input-xxlarge { 253 | width: 530px; 254 | } 255 | input[class*="span"], 256 | select[class*="span"], 257 | textarea[class*="span"], 258 | .uneditable-input[class*="span"], 259 | .row-fluid input[class*="span"], 260 | .row-fluid select[class*="span"], 261 | .row-fluid textarea[class*="span"], 262 | .row-fluid .uneditable-input[class*="span"] { 263 | float: none; 264 | margin-left: 0; 265 | } 266 | .input-append input[class*="span"], 267 | .input-append .uneditable-input[class*="span"], 268 | .input-prepend input[class*="span"], 269 | .input-prepend .uneditable-input[class*="span"], 270 | .row-fluid .input-prepend [class*="span"], 271 | .row-fluid .input-append [class*="span"] { 272 | display: inline-block; 273 | } 274 | input, 275 | textarea, 276 | .uneditable-input { 277 | margin-left: 0; 278 | } 279 | input.span12, textarea.span12, .uneditable-input.span12 { 280 | width: 930px; 281 | } 282 | input.span11, textarea.span11, .uneditable-input.span11 { 283 | width: 850px; 284 | } 285 | input.span10, textarea.span10, .uneditable-input.span10 { 286 | width: 770px; 287 | } 288 | input.span9, textarea.span9, .uneditable-input.span9 { 289 | width: 690px; 290 | } 291 | input.span8, textarea.span8, .uneditable-input.span8 { 292 | width: 610px; 293 | } 294 | input.span7, textarea.span7, .uneditable-input.span7 { 295 | width: 530px; 296 | } 297 | input.span6, textarea.span6, .uneditable-input.span6 { 298 | width: 450px; 299 | } 300 | input.span5, textarea.span5, .uneditable-input.span5 { 301 | width: 370px; 302 | } 303 | input.span4, textarea.span4, .uneditable-input.span4 { 304 | width: 290px; 305 | } 306 | input.span3, textarea.span3, .uneditable-input.span3 { 307 | width: 210px; 308 | } 309 | input.span2, textarea.span2, .uneditable-input.span2 { 310 | width: 130px; 311 | } 312 | input.span1, textarea.span1, .uneditable-input.span1 { 313 | width: 50px; 314 | } 315 | input[disabled], 316 | select[disabled], 317 | textarea[disabled], 318 | input[readonly], 319 | select[readonly], 320 | textarea[readonly] { 321 | cursor: not-allowed; 322 | background-color: #eeeeee; 323 | border-color: #ddd; 324 | } 325 | input[type="radio"][disabled], 326 | input[type="checkbox"][disabled], 327 | input[type="radio"][readonly], 328 | input[type="checkbox"][readonly] { 329 | background-color: transparent; 330 | } 331 | .control-group.warning > label, 332 | .control-group.warning .help-block, 333 | .control-group.warning .help-inline { 334 | color: #c09853; 335 | } 336 | .control-group.warning .checkbox, 337 | .control-group.warning .radio, 338 | .control-group.warning input, 339 | .control-group.warning select, 340 | .control-group.warning textarea { 341 | color: #c09853; 342 | border-color: #c09853; 343 | } 344 | .control-group.warning .checkbox:focus, 345 | .control-group.warning .radio:focus, 346 | .control-group.warning input:focus, 347 | .control-group.warning select:focus, 348 | .control-group.warning textarea:focus { 349 | border-color: #a47e3c; 350 | -webkit-box-shadow: 0 0 6px #dbc59e; 351 | -moz-box-shadow: 0 0 6px #dbc59e; 352 | box-shadow: 0 0 6px #dbc59e; 353 | } 354 | .control-group.warning .input-prepend .add-on, 355 | .control-group.warning .input-append .add-on { 356 | color: #c09853; 357 | background-color: #fcf8e3; 358 | border-color: #c09853; 359 | } 360 | .control-group.error > label, 361 | .control-group.error .help-block, 362 | .control-group.error .help-inline { 363 | color: #b94a48; 364 | } 365 | .control-group.error .checkbox, 366 | .control-group.error .radio, 367 | .control-group.error input, 368 | .control-group.error select, 369 | .control-group.error textarea { 370 | color: #b94a48; 371 | border-color: #b94a48; 372 | } 373 | .control-group.error .checkbox:focus, 374 | .control-group.error .radio:focus, 375 | .control-group.error input:focus, 376 | .control-group.error select:focus, 377 | .control-group.error textarea:focus { 378 | border-color: #953b39; 379 | -webkit-box-shadow: 0 0 6px #d59392; 380 | -moz-box-shadow: 0 0 6px #d59392; 381 | box-shadow: 0 0 6px #d59392; 382 | } 383 | .control-group.error .input-prepend .add-on, 384 | .control-group.error .input-append .add-on { 385 | color: #b94a48; 386 | background-color: #f2dede; 387 | border-color: #b94a48; 388 | } 389 | .control-group.success > label, 390 | .control-group.success .help-block, 391 | .control-group.success .help-inline { 392 | color: #468847; 393 | } 394 | .control-group.success .checkbox, 395 | .control-group.success .radio, 396 | .control-group.success input, 397 | .control-group.success select, 398 | .control-group.success textarea { 399 | color: #468847; 400 | border-color: #468847; 401 | } 402 | .control-group.success .checkbox:focus, 403 | .control-group.success .radio:focus, 404 | .control-group.success input:focus, 405 | .control-group.success select:focus, 406 | .control-group.success textarea:focus { 407 | border-color: #356635; 408 | -webkit-box-shadow: 0 0 6px #7aba7b; 409 | -moz-box-shadow: 0 0 6px #7aba7b; 410 | box-shadow: 0 0 6px #7aba7b; 411 | } 412 | .control-group.success .input-prepend .add-on, 413 | .control-group.success .input-append .add-on { 414 | color: #468847; 415 | background-color: #dff0d8; 416 | border-color: #468847; 417 | } 418 | input:focus:required:invalid, 419 | textarea:focus:required:invalid, 420 | select:focus:required:invalid { 421 | color: #b94a48; 422 | border-color: #ee5f5b; 423 | } 424 | input:focus:required:invalid:focus, 425 | textarea:focus:required:invalid:focus, 426 | select:focus:required:invalid:focus { 427 | border-color: #e9322d; 428 | -webkit-box-shadow: 0 0 6px #f8b9b7; 429 | -moz-box-shadow: 0 0 6px #f8b9b7; 430 | box-shadow: 0 0 6px #f8b9b7; 431 | } 432 | .form-actions { 433 | padding: 17px 20px 18px; 434 | margin-top: 18px; 435 | margin-bottom: 18px; 436 | background-color: #f5f5f5; 437 | border-top: 1px solid #e5e5e5; 438 | *zoom: 1; 439 | } 440 | .form-actions:before, 441 | .form-actions:after { 442 | display: table; 443 | content: ""; 444 | } 445 | .form-actions:after { 446 | clear: both; 447 | } 448 | .uneditable-input { 449 | overflow: hidden; 450 | white-space: nowrap; 451 | cursor: not-allowed; 452 | background-color: #ffffff; 453 | border-color: #eee; 454 | -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); 455 | -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); 456 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); 457 | } 458 | :-moz-placeholder { 459 | color: #999999; 460 | } 461 | :-ms-input-placeholder { 462 | color: #999999; 463 | } 464 | ::-webkit-input-placeholder { 465 | color: #999999; 466 | } 467 | .help-block, 468 | .help-inline { 469 | color: #555555; 470 | } 471 | .help-block { 472 | display: block; 473 | margin-bottom: 9px; 474 | } 475 | .help-inline { 476 | display: inline-block; 477 | *display: inline; 478 | /* IE7 inline-block hack */ 479 | 480 | *zoom: 1; 481 | vertical-align: middle; 482 | padding-left: 5px; 483 | } 484 | .input-prepend, 485 | .input-append { 486 | margin-bottom: 5px; 487 | } 488 | .input-prepend input, 489 | .input-append input, 490 | .input-prepend select, 491 | .input-append select, 492 | .input-prepend .uneditable-input, 493 | .input-append .uneditable-input { 494 | position: relative; 495 | margin-bottom: 0; 496 | *margin-left: 0; 497 | vertical-align: middle; 498 | -webkit-border-radius: 0 3px 3px 0; 499 | -moz-border-radius: 0 3px 3px 0; 500 | border-radius: 0 3px 3px 0; 501 | } 502 | .input-prepend input:focus, 503 | .input-append input:focus, 504 | .input-prepend select:focus, 505 | .input-append select:focus, 506 | .input-prepend .uneditable-input:focus, 507 | .input-append .uneditable-input:focus { 508 | z-index: 2; 509 | } 510 | .input-prepend .uneditable-input, 511 | .input-append .uneditable-input { 512 | border-left-color: #ccc; 513 | } 514 | .input-prepend .add-on, 515 | .input-append .add-on { 516 | display: inline-block; 517 | width: auto; 518 | height: 18px; 519 | min-width: 16px; 520 | padding: 4px 5px; 521 | font-weight: normal; 522 | line-height: 18px; 523 | text-align: center; 524 | text-shadow: 0 1px 0 #ffffff; 525 | vertical-align: middle; 526 | background-color: #eeeeee; 527 | border: 1px solid #ccc; 528 | } 529 | .input-prepend .add-on, 530 | .input-append .add-on, 531 | .input-prepend .btn, 532 | .input-append .btn { 533 | margin-left: -1px; 534 | -webkit-border-radius: 0; 535 | -moz-border-radius: 0; 536 | border-radius: 0; 537 | } 538 | .input-prepend .active, 539 | .input-append .active { 540 | background-color: #a9dba9; 541 | border-color: #46a546; 542 | } 543 | .input-prepend .add-on, 544 | .input-prepend .btn { 545 | margin-right: -1px; 546 | } 547 | .input-prepend .add-on:first-child, 548 | .input-prepend .btn:first-child { 549 | -webkit-border-radius: 3px 0 0 3px; 550 | -moz-border-radius: 3px 0 0 3px; 551 | border-radius: 3px 0 0 3px; 552 | } 553 | .input-append input, 554 | .input-append select, 555 | .input-append .uneditable-input { 556 | -webkit-border-radius: 3px 0 0 3px; 557 | -moz-border-radius: 3px 0 0 3px; 558 | border-radius: 3px 0 0 3px; 559 | } 560 | .input-append .uneditable-input { 561 | border-right-color: #ccc; 562 | border-left-color: #eee; 563 | } 564 | .input-append .add-on:last-child, 565 | .input-append .btn:last-child { 566 | -webkit-border-radius: 0 3px 3px 0; 567 | -moz-border-radius: 0 3px 3px 0; 568 | border-radius: 0 3px 3px 0; 569 | } 570 | .input-prepend.input-append input, 571 | .input-prepend.input-append select, 572 | .input-prepend.input-append .uneditable-input { 573 | -webkit-border-radius: 0; 574 | -moz-border-radius: 0; 575 | border-radius: 0; 576 | } 577 | .input-prepend.input-append .add-on:first-child, 578 | .input-prepend.input-append .btn:first-child { 579 | margin-right: -1px; 580 | -webkit-border-radius: 3px 0 0 3px; 581 | -moz-border-radius: 3px 0 0 3px; 582 | border-radius: 3px 0 0 3px; 583 | } 584 | .input-prepend.input-append .add-on:last-child, 585 | .input-prepend.input-append .btn:last-child { 586 | margin-left: -1px; 587 | -webkit-border-radius: 0 3px 3px 0; 588 | -moz-border-radius: 0 3px 3px 0; 589 | border-radius: 0 3px 3px 0; 590 | } 591 | .search-query { 592 | padding-right: 14px; 593 | padding-right: 4px \9; 594 | padding-left: 14px; 595 | padding-left: 4px \9; 596 | /* IE7-8 doesn't have border-radius, so don't indent the padding */ 597 | 598 | margin-bottom: 0; 599 | -webkit-border-radius: 14px; 600 | -moz-border-radius: 14px; 601 | border-radius: 14px; 602 | } 603 | .form-search input, 604 | .form-inline input, 605 | .form-horizontal input, 606 | .form-search textarea, 607 | .form-inline textarea, 608 | .form-horizontal textarea, 609 | .form-search select, 610 | .form-inline select, 611 | .form-horizontal select, 612 | .form-search .help-inline, 613 | .form-inline .help-inline, 614 | .form-horizontal .help-inline, 615 | .form-search .uneditable-input, 616 | .form-inline .uneditable-input, 617 | .form-horizontal .uneditable-input, 618 | .form-search .input-prepend, 619 | .form-inline .input-prepend, 620 | .form-horizontal .input-prepend, 621 | .form-search .input-append, 622 | .form-inline .input-append, 623 | .form-horizontal .input-append { 624 | display: inline-block; 625 | *display: inline; 626 | /* IE7 inline-block hack */ 627 | 628 | *zoom: 1; 629 | margin-bottom: 0; 630 | } 631 | .form-search .hide, 632 | .form-inline .hide, 633 | .form-horizontal .hide { 634 | display: none; 635 | } 636 | .form-search label, 637 | .form-inline label { 638 | display: inline-block; 639 | } 640 | .form-search .input-append, 641 | .form-inline .input-append, 642 | .form-search .input-prepend, 643 | .form-inline .input-prepend { 644 | margin-bottom: 0; 645 | } 646 | .form-search .radio, 647 | .form-search .checkbox, 648 | .form-inline .radio, 649 | .form-inline .checkbox { 650 | padding-left: 0; 651 | margin-bottom: 0; 652 | vertical-align: middle; 653 | } 654 | .form-search .radio input[type="radio"], 655 | .form-search .checkbox input[type="checkbox"], 656 | .form-inline .radio input[type="radio"], 657 | .form-inline .checkbox input[type="checkbox"] { 658 | float: left; 659 | margin-right: 3px; 660 | margin-left: 0; 661 | } 662 | .control-group { 663 | margin-bottom: 9px; 664 | } 665 | legend + .control-group { 666 | margin-top: 18px; 667 | -webkit-margin-top-collapse: separate; 668 | } 669 | .form-horizontal .control-group { 670 | margin-bottom: 18px; 671 | *zoom: 1; 672 | } 673 | .form-horizontal .control-group:before, 674 | .form-horizontal .control-group:after { 675 | display: table; 676 | content: ""; 677 | } 678 | .form-horizontal .control-group:after { 679 | clear: both; 680 | } 681 | .form-horizontal .control-label { 682 | float: left; 683 | width: 140px; 684 | padding-top: 5px; 685 | text-align: right; 686 | } 687 | .form-horizontal .controls { 688 | *display: inline-block; 689 | *padding-left: 20px; 690 | margin-left: 160px; 691 | *margin-left: 0; 692 | } 693 | .form-horizontal .controls:first-child { 694 | *padding-left: 160px; 695 | } 696 | .form-horizontal .help-block { 697 | margin-top: 9px; 698 | margin-bottom: 0; 699 | } 700 | .form-horizontal .form-actions { 701 | padding-left: 160px; 702 | } 703 | .btn { 704 | display: inline-block; 705 | *display: inline; 706 | /* IE7 inline-block hack */ 707 | 708 | *zoom: 1; 709 | padding: 4px 10px 4px; 710 | margin-bottom: 0; 711 | font-size: 13px; 712 | line-height: 18px; 713 | *line-height: 20px; 714 | color: #333333; 715 | text-align: center; 716 | text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); 717 | vertical-align: middle; 718 | cursor: pointer; 719 | background-color: #f5f5f5; 720 | background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); 721 | background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6); 722 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); 723 | background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); 724 | background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); 725 | background-image: linear-gradient(top, #ffffff, #e6e6e6); 726 | background-repeat: repeat-x; 727 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0); 728 | border-color: #e6e6e6 #e6e6e6 #bfbfbf; 729 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 730 | *background-color: #e6e6e6; 731 | /* Darken IE7 buttons by default so they stand out more given they won't have borders */ 732 | 733 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 734 | border: 1px solid #cccccc; 735 | *border: 0; 736 | border-bottom-color: #b3b3b3; 737 | -webkit-border-radius: 4px; 738 | -moz-border-radius: 4px; 739 | border-radius: 4px; 740 | *margin-left: .3em; 741 | -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); 742 | -moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); 743 | box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); 744 | } 745 | .btn:hover, 746 | .btn:active, 747 | .btn.active, 748 | .btn.disabled, 749 | .btn[disabled] { 750 | background-color: #e6e6e6; 751 | *background-color: #d9d9d9; 752 | } 753 | .btn:active, 754 | .btn.active { 755 | background-color: #cccccc \9; 756 | } 757 | .btn:first-child { 758 | *margin-left: 0; 759 | } 760 | .btn:hover { 761 | color: #333333; 762 | text-decoration: none; 763 | background-color: #e6e6e6; 764 | *background-color: #d9d9d9; 765 | /* Buttons in IE7 don't get borders, so darken on hover */ 766 | 767 | background-position: 0 -15px; 768 | -webkit-transition: background-position 0.1s linear; 769 | -moz-transition: background-position 0.1s linear; 770 | -ms-transition: background-position 0.1s linear; 771 | -o-transition: background-position 0.1s linear; 772 | transition: background-position 0.1s linear; 773 | } 774 | .btn:focus { 775 | outline: thin dotted #333; 776 | outline: 5px auto -webkit-focus-ring-color; 777 | outline-offset: -2px; 778 | } 779 | .btn.active, 780 | .btn:active { 781 | background-color: #e6e6e6; 782 | background-color: #d9d9d9 \9; 783 | background-image: none; 784 | outline: 0; 785 | -webkit-box-shadow: inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05); 786 | -moz-box-shadow: inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05); 787 | box-shadow: inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05); 788 | } 789 | .btn.disabled, 790 | .btn[disabled] { 791 | cursor: default; 792 | background-color: #e6e6e6; 793 | background-image: none; 794 | opacity: 0.65; 795 | filter: alpha(opacity=65); 796 | -webkit-box-shadow: none; 797 | -moz-box-shadow: none; 798 | box-shadow: none; 799 | } 800 | .btn-large { 801 | padding: 9px 14px; 802 | font-size: 15px; 803 | line-height: normal; 804 | -webkit-border-radius: 5px; 805 | -moz-border-radius: 5px; 806 | border-radius: 5px; 807 | } 808 | .btn-large [class^="icon-"] { 809 | margin-top: 1px; 810 | } 811 | .btn-small { 812 | padding: 5px 9px; 813 | font-size: 11px; 814 | line-height: 16px; 815 | } 816 | .btn-small [class^="icon-"] { 817 | margin-top: -1px; 818 | } 819 | .btn-mini { 820 | padding: 2px 6px; 821 | font-size: 11px; 822 | line-height: 14px; 823 | } 824 | .btn-primary, 825 | .btn-primary:hover, 826 | .btn-warning, 827 | .btn-warning:hover, 828 | .btn-danger, 829 | .btn-danger:hover, 830 | .btn-success, 831 | .btn-success:hover, 832 | .btn-info, 833 | .btn-info:hover, 834 | .btn-inverse, 835 | .btn-inverse:hover { 836 | color: #ffffff; 837 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 838 | } 839 | .btn-primary.active, 840 | .btn-warning.active, 841 | .btn-danger.active, 842 | .btn-success.active, 843 | .btn-info.active, 844 | .btn-inverse.active { 845 | color: rgba(255, 255, 255, 0.75); 846 | } 847 | .btn { 848 | border-color: #ccc; 849 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 850 | } 851 | .btn-primary { 852 | background-color: #0074cc; 853 | background-image: -moz-linear-gradient(top, #0088cc, #0055cc); 854 | background-image: -ms-linear-gradient(top, #0088cc, #0055cc); 855 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0055cc)); 856 | background-image: -webkit-linear-gradient(top, #0088cc, #0055cc); 857 | background-image: -o-linear-gradient(top, #0088cc, #0055cc); 858 | background-image: linear-gradient(top, #0088cc, #0055cc); 859 | background-repeat: repeat-x; 860 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0055cc', GradientType=0); 861 | border-color: #0055cc #0055cc #003580; 862 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 863 | *background-color: #0055cc; 864 | /* Darken IE7 buttons by default so they stand out more given they won't have borders */ 865 | 866 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 867 | } 868 | .btn-primary:hover, 869 | .btn-primary:active, 870 | .btn-primary.active, 871 | .btn-primary.disabled, 872 | .btn-primary[disabled] { 873 | background-color: #0055cc; 874 | *background-color: #004ab3; 875 | } 876 | .btn-primary:active, 877 | .btn-primary.active { 878 | background-color: #004099 \9; 879 | } 880 | .btn-warning { 881 | background-color: #faa732; 882 | background-image: -moz-linear-gradient(top, #fbb450, #f89406); 883 | background-image: -ms-linear-gradient(top, #fbb450, #f89406); 884 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406)); 885 | background-image: -webkit-linear-gradient(top, #fbb450, #f89406); 886 | background-image: -o-linear-gradient(top, #fbb450, #f89406); 887 | background-image: linear-gradient(top, #fbb450, #f89406); 888 | background-repeat: repeat-x; 889 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0); 890 | border-color: #f89406 #f89406 #ad6704; 891 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 892 | *background-color: #f89406; 893 | /* Darken IE7 buttons by default so they stand out more given they won't have borders */ 894 | 895 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 896 | } 897 | .btn-warning:hover, 898 | .btn-warning:active, 899 | .btn-warning.active, 900 | .btn-warning.disabled, 901 | .btn-warning[disabled] { 902 | background-color: #f89406; 903 | *background-color: #df8505; 904 | } 905 | .btn-warning:active, 906 | .btn-warning.active { 907 | background-color: #c67605 \9; 908 | } 909 | .btn-danger { 910 | background-color: #da4f49; 911 | background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f); 912 | background-image: -ms-linear-gradient(top, #ee5f5b, #bd362f); 913 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f)); 914 | background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f); 915 | background-image: -o-linear-gradient(top, #ee5f5b, #bd362f); 916 | background-image: linear-gradient(top, #ee5f5b, #bd362f); 917 | background-repeat: repeat-x; 918 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#bd362f', GradientType=0); 919 | border-color: #bd362f #bd362f #802420; 920 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 921 | *background-color: #bd362f; 922 | /* Darken IE7 buttons by default so they stand out more given they won't have borders */ 923 | 924 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 925 | } 926 | .btn-danger:hover, 927 | .btn-danger:active, 928 | .btn-danger.active, 929 | .btn-danger.disabled, 930 | .btn-danger[disabled] { 931 | background-color: #bd362f; 932 | *background-color: #a9302a; 933 | } 934 | .btn-danger:active, 935 | .btn-danger.active { 936 | background-color: #942a25 \9; 937 | } 938 | .btn-success { 939 | background-color: #5bb75b; 940 | background-image: -moz-linear-gradient(top, #62c462, #51a351); 941 | background-image: -ms-linear-gradient(top, #62c462, #51a351); 942 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351)); 943 | background-image: -webkit-linear-gradient(top, #62c462, #51a351); 944 | background-image: -o-linear-gradient(top, #62c462, #51a351); 945 | background-image: linear-gradient(top, #62c462, #51a351); 946 | background-repeat: repeat-x; 947 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#51a351', GradientType=0); 948 | border-color: #51a351 #51a351 #387038; 949 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 950 | *background-color: #51a351; 951 | /* Darken IE7 buttons by default so they stand out more given they won't have borders */ 952 | 953 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 954 | } 955 | .btn-success:hover, 956 | .btn-success:active, 957 | .btn-success.active, 958 | .btn-success.disabled, 959 | .btn-success[disabled] { 960 | background-color: #51a351; 961 | *background-color: #499249; 962 | } 963 | .btn-success:active, 964 | .btn-success.active { 965 | background-color: #408140 \9; 966 | } 967 | .btn-info { 968 | background-color: #49afcd; 969 | background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4); 970 | background-image: -ms-linear-gradient(top, #5bc0de, #2f96b4); 971 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4)); 972 | background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4); 973 | background-image: -o-linear-gradient(top, #5bc0de, #2f96b4); 974 | background-image: linear-gradient(top, #5bc0de, #2f96b4); 975 | background-repeat: repeat-x; 976 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#2f96b4', GradientType=0); 977 | border-color: #2f96b4 #2f96b4 #1f6377; 978 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 979 | *background-color: #2f96b4; 980 | /* Darken IE7 buttons by default so they stand out more given they won't have borders */ 981 | 982 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 983 | } 984 | .btn-info:hover, 985 | .btn-info:active, 986 | .btn-info.active, 987 | .btn-info.disabled, 988 | .btn-info[disabled] { 989 | background-color: #2f96b4; 990 | *background-color: #2a85a0; 991 | } 992 | .btn-info:active, 993 | .btn-info.active { 994 | background-color: #24748c \9; 995 | } 996 | .btn-inverse { 997 | background-color: #414141; 998 | background-image: -moz-linear-gradient(top, #555555, #222222); 999 | background-image: -ms-linear-gradient(top, #555555, #222222); 1000 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#555555), to(#222222)); 1001 | background-image: -webkit-linear-gradient(top, #555555, #222222); 1002 | background-image: -o-linear-gradient(top, #555555, #222222); 1003 | background-image: linear-gradient(top, #555555, #222222); 1004 | background-repeat: repeat-x; 1005 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#555555', endColorstr='#222222', GradientType=0); 1006 | border-color: #222222 #222222 #000000; 1007 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 1008 | *background-color: #222222; 1009 | /* Darken IE7 buttons by default so they stand out more given they won't have borders */ 1010 | 1011 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 1012 | } 1013 | .btn-inverse:hover, 1014 | .btn-inverse:active, 1015 | .btn-inverse.active, 1016 | .btn-inverse.disabled, 1017 | .btn-inverse[disabled] { 1018 | background-color: #222222; 1019 | *background-color: #151515; 1020 | } 1021 | .btn-inverse:active, 1022 | .btn-inverse.active { 1023 | background-color: #080808 \9; 1024 | } 1025 | button.btn, 1026 | input[type="submit"].btn { 1027 | *padding-top: 2px; 1028 | *padding-bottom: 2px; 1029 | } 1030 | button.btn::-moz-focus-inner, 1031 | input[type="submit"].btn::-moz-focus-inner { 1032 | padding: 0; 1033 | border: 0; 1034 | } 1035 | button.btn.btn-large, 1036 | input[type="submit"].btn.btn-large { 1037 | *padding-top: 7px; 1038 | *padding-bottom: 7px; 1039 | } 1040 | button.btn.btn-small, 1041 | input[type="submit"].btn.btn-small { 1042 | *padding-top: 3px; 1043 | *padding-bottom: 3px; 1044 | } 1045 | button.btn.btn-mini, 1046 | input[type="submit"].btn.btn-mini { 1047 | *padding-top: 1px; 1048 | *padding-bottom: 1px; 1049 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/reset.css: -------------------------------------------------------------------------------- 1 | /* YUI 3.5.0 reset.css (http://developer.yahoo.com/yui/3/cssreset/) - http://cssreset.com */ 2 | html{color:#000;background:#FFF}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal}ol,ul{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}q:before,q:after{content:''}abbr,acronym{border:0;font-variant:normal}sup{vertical-align:text-top}sub{vertical-align:text-bottom}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit}input,textarea,select{*font-size:100%}legend{color:#000}#yui3-css-stamp.cssreset{display:none} -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | 4 | before_filter :ref_to_cookie 5 | 6 | def mobile_device? 7 | if session[:mobile_param] 8 | session[:mobile_param] == '1' 9 | else 10 | request.user_agent =~ /Mobile|webOS/ 11 | end 12 | end 13 | 14 | protected 15 | 16 | def ref_to_cookie 17 | campaign_ended = Rails.application.config.ended 18 | return if campaign_ended || !params[:ref] 19 | 20 | unless User.find_by_referral_code(params[:ref]).nil? 21 | h_ref = { value: params[:ref], expires: 1.week.from_now } 22 | cookies[:h_ref] = h_ref 23 | end 24 | 25 | user_agent = request.env['HTTP_USER_AGENT'] 26 | return unless user_agent && !user_agent.include?('facebookexternalhit/1.1') 27 | redirect_to proc { url_for(params.except(:ref)) } 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | before_filter :skip_first_page, only: :new 3 | before_filter :handle_ip, only: :create 4 | 5 | def new 6 | @bodyId = 'home' 7 | @is_mobile = mobile_device? 8 | 9 | @user = User.new 10 | 11 | respond_to do |format| 12 | format.html # new.html.erb 13 | end 14 | end 15 | 16 | def create 17 | ref_code = cookies[:h_ref] 18 | email = params[:user][:email] 19 | @user = User.new(email: email) 20 | @user.referrer = User.find_by_referral_code(ref_code) if ref_code 21 | 22 | if @user.save 23 | cookies[:h_email] = { value: @user.email } 24 | redirect_to '/refer-a-friend' 25 | else 26 | logger.info("Error saving user with email, #{email}") 27 | redirect_to root_path, alert: 'Something went wrong!' 28 | end 29 | end 30 | 31 | def refer 32 | @bodyId = 'refer' 33 | @is_mobile = mobile_device? 34 | 35 | @user = User.find_by_email(cookies[:h_email]) 36 | 37 | respond_to do |format| 38 | if @user.nil? 39 | format.html { redirect_to root_path, alert: 'Something went wrong!' } 40 | else 41 | format.html # refer.html.erb 42 | end 43 | end 44 | end 45 | 46 | def policy 47 | end 48 | 49 | def redirect 50 | redirect_to root_path, status: 404 51 | end 52 | 53 | private 54 | 55 | def skip_first_page 56 | return if Rails.application.config.ended 57 | 58 | email = cookies[:h_email] 59 | if email && User.find_by_email(email) 60 | redirect_to '/refer-a-friend' 61 | else 62 | cookies.delete :h_email 63 | end 64 | end 65 | 66 | def handle_ip 67 | # Prevent someone from gaming the site by referring themselves. 68 | # Presumably, users are doing this from the same device so block 69 | # their ip after their ip appears three times in the database. 70 | 71 | address = request.env['HTTP_X_FORWARDED_FOR'] 72 | return if address.nil? 73 | 74 | current_ip = IpAddress.find_by_address(address) 75 | if current_ip.nil? 76 | current_ip = IpAddress.create(address: address, count: 1) 77 | elsif current_ip.count > 2 78 | logger.info('IP address has already appeared three times in our records. 79 | Redirecting user back to landing page.') 80 | return redirect_to root_path 81 | else 82 | current_ip.count += 1 83 | current_ip.save 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | module UsersHelper 2 | def self.unused_referral_code 3 | referral_code = SecureRandom.hex(5) 4 | collision = User.find_by_referral_code(referral_code) 5 | 6 | until collision.nil? 7 | referral_code = SecureRandom.hex(5) 8 | collision = User.find_by_referral_code(referral_code) 9 | end 10 | referral_code 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/mailers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/mailers/.gitkeep -------------------------------------------------------------------------------- /app/mailers/user_mailer.rb: -------------------------------------------------------------------------------- 1 | class UserMailer < ActionMailer::Base 2 | default from: "Harry's " 3 | 4 | def signup_email(user) 5 | @user = user 6 | @twitter_message = "#Shaving is evolving. Excited for @harrys to launch." 7 | 8 | mail(:to => user.email, :subject => "Thanks for signing up!") 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/app/models/.gitkeep -------------------------------------------------------------------------------- /app/models/admin_user.rb: -------------------------------------------------------------------------------- 1 | class AdminUser < ActiveRecord::Base 2 | # Include default devise modules. Others available are: 3 | # :token_authenticatable, :confirmable, 4 | # :lockable, :timeoutable and :omniauthable 5 | devise :database_authenticatable, 6 | :recoverable, :rememberable, :trackable, :validatable 7 | 8 | # Setup accessible (or protected) attributes for your model 9 | # attr_accessible :title, :body 10 | end 11 | -------------------------------------------------------------------------------- /app/models/ip_address.rb: -------------------------------------------------------------------------------- 1 | class IpAddress < ActiveRecord::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | require 'users_helper' 2 | 3 | class User < ActiveRecord::Base 4 | belongs_to :referrer, class_name: 'User', foreign_key: 'referrer_id' 5 | has_many :referrals, class_name: 'User', foreign_key: 'referrer_id' 6 | 7 | validates :email, presence: true, uniqueness: true, format: { 8 | with: /\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/i, 9 | message: 'Invalid email format.' 10 | } 11 | validates :referral_code, uniqueness: true 12 | 13 | before_create :create_referral_code 14 | after_create :send_welcome_email 15 | 16 | REFERRAL_STEPS = [ 17 | { 18 | 'count' => 5, 19 | 'html' => 'Shave
Cream', 20 | 'class' => 'two', 21 | 'image' => ActionController::Base.helpers.asset_path( 22 | 'refer/cream-tooltip@2x.png') 23 | }, 24 | { 25 | 'count' => 10, 26 | 'html' => 'Truman Handle
w/ Blade', 27 | 'class' => 'three', 28 | 'image' => ActionController::Base.helpers.asset_path( 29 | 'refer/truman@2x.png') 30 | }, 31 | { 32 | 'count' => 25, 33 | 'html' => 'Winston
Shave Set', 34 | 'class' => 'four', 35 | 'image' => ActionController::Base.helpers.asset_path( 36 | 'refer/winston@2x.png') 37 | }, 38 | { 39 | 'count' => 50, 40 | 'html' => 'One Year
Free Blades', 41 | 'class' => 'five', 42 | 'image' => ActionController::Base.helpers.asset_path( 43 | 'refer/blade-explain@2x.png') 44 | } 45 | ] 46 | 47 | private 48 | 49 | def create_referral_code 50 | self.referral_code = UsersHelper.unused_referral_code 51 | end 52 | 53 | def send_welcome_email 54 | UserMailer.delay.signup_email(self) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Harry’s – Tell Friends, Shave for Free 9 | 10 | 11 | 12 | 13 | 14 | "/> 15 | 16 | <% if @is_mobile %> 17 | <% if @bodyId == 'home' %> 18 | 19 | <% else %> 20 | 21 | <% end %> 22 | <% end %> 23 | <%= stylesheet_link_tag "application", :media => "all" %> 24 | <%= csrf_meta_tags %> 25 | 26 | 27 | 28 | <%= yield %> 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/views/user_mailer/signup_email.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | You'll be first to know when we launch. Shaving is evolving. Tell your friends and shave for free. 13 | 14 | 15 | 97 | 98 |
16 | 17 | 18 | 23 | 24 |
19 |
20 | Harry's 21 |
22 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
   
32 | 33 | 34 | 39 | 40 |
35 |
36 | Harry's Is Live! We're launcing a new shave brand. You'll be the first to know. 37 |
38 |
41 | 42 | 43 | 48 | 49 |
44 |
45 | Refer Friends, Earn Rewards 46 |
47 |
50 | 51 | 52 | 57 | 58 |
53 |
54 | ?ref=<%= @user.referral_code %>" style="font-family: 'Times', serif; font-size: 14px; letter-spacing: 0.05em; color: #6c6c6c; text-decoration: none" target="_blank"><%= Rails.application.config.action_mailer.default_url_options || "" %>?ref=<%= @user.referral_code %> 55 |
56 |
59 | 60 | 61 | 78 | 79 |
62 | 63 | 64 | 69 | 70 | 75 | 76 |
65 | ?ref=<%= CGI::escape(@user.referral_code) %>&title=Harrys" style="outline: 0; border: 0;" target="_blank"> 66 | Share on Facebook 67 | 68 | 71 | ?ref=<%= CGI::escape(@user.referral_code) %>&text=<%= CGI::escape(@twitter_message) %>" style="outline: 0; border: 0;" target="_blank"> 72 | Share on Twitter 73 | 74 |
77 |
80 | 81 | 82 | 83 | 84 | 85 | 86 |
   
87 | 88 | 89 | 94 | 95 |
90 |
91 | Harrys.com | (888) 212-6855 92 |
93 |
96 |
99 | 100 | -------------------------------------------------------------------------------- /app/views/users/_campaign_ended.html.erb: -------------------------------------------------------------------------------- 1 |

THANK YOU FOR PARTICIPATING
IN OUR PRE-LAUNCH.

2 |

WE ARE COMPILING REWARDS RIGHT NOW AND WILL EMAIL
COUPON CODES TO ALL RECIPIENTS IN THE NEXT DAY OR TWO

3 |

In the meantime, head to WWW.HARRYS.COM

4 |

Thank You Again!

-------------------------------------------------------------------------------- /app/views/users/_campaign_ongoing.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | <%= form_for @user, :url => { :action => "create" } do |f| %> 4 | <%= f.text_field :email, :placeholder => "Enter Email", :class => "email brandon" %> 5 | 6 | <% end %> -------------------------------------------------------------------------------- /app/views/users/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Harry’s is live

3 |

Respecting the Face and Wallet
Since Like...Right Now.

4 |
5 | 6 |
"> 7 | <% if Rails.application.config.ended %> 8 | <%= render "users/campaign_ended" %> 9 | <% else %> 10 | <%= render "users/campaign_ongoing" %> 11 | <% end %> 12 |
-------------------------------------------------------------------------------- /app/views/users/policy.html.erb: -------------------------------------------------------------------------------- 1 | 10 | 11 |

12 | PRIVACY POLICY 13 |

14 |

15 | ADKM, Inc. (Harry’s) considers the privacy and security of user information an important component of the services offered at its 16 | website, www.harry’s.com (the Site). The following information explains how Harry’s collects and uses information obtained from 17 | users in connection with services available at the Site (taken together, the Service). 18 |

19 |

20 | Information Collected. 21 |

22 |

23 | At your election, you may request additional information about Harry’s. When you do so, you may need to submit certain information or data to Harry’s, for 24 | example, your contact information (collectively, User Data). 25 |

26 |

27 | When you visit the Site, web servers collect "traffic data" (such as, for example, time and date, the address of the website from which you entered the 28 | Site) about your visit, which is stored as anonymous, aggregate data. Collecting such data may entail the use of IP addresses or other numeric codes used 29 | to identify a computer. 30 |

31 |

32 | Use of Information. 33 |

34 |

35 | Harry’s uses User Data to respond to your inquiries for additional information. 36 |

37 |

38 | Harry’s uses "traffic data" to help diagnose problems with its server, analyze trends and administer the Site. Your IP address is not linked to personally 39 | identifiable information, but is used to gather broad demographic data and to monitor statistics to improve the Site and Service. 40 |

41 |

42 | Sharing of Information. 43 |

44 |

45 | Currently, Harry’s will not share personally identifiable information with any third party for commercial purposes. Harry’s may, however, disclose 46 | personally identifiable data if (1) reasonably necessary to perform the Service, (2) authorized by you, (3) otherwise permitted under this Privacy Policy 47 | or (4) Harry’s is required to do so by law or regulation, or in the good faith belief that such action is necessary to (i) conform or comply with 48 | applicable laws, regulations or legal process, (ii) protect or defend the rights or property of Harry’s or any other user or (iii) enforce the Terms of 49 | Use. 50 |

51 |

52 | Harry’s may transfer personally identifiable information to any successor to all or substantially all of its business or assets that concern the Service. 53 |

54 |

55 | Security. 56 |

57 |

58 | Information collected by Harry’s is stored in secure operating environments that are not made generally available to the public. Unfortunately, no data 59 | transmission over the Internet can be guaranteed to be 100% secure. As a result, Harry’s cannot ensure the security of any information you provide, and you 60 | do so at your own risk. Once Harry’s receives your transmission, it will make reasonable efforts to ensure its security on its systems. 61 |

62 |

63 | Third Party Sites. 64 |

65 |

66 | The Site may permit you to link to other websites on the Internet, and other websites may contain links to the Site. These other websites are not under 67 | Harry’s control. The privacy and security practices of websites linked to or from the Site are not covered by this Privacy Policy, and Harry’s is not 68 | responsible for the privacy or security practices or the content of such websites. 69 |

70 |

71 | IP Addresses and Cookies. 72 |

73 |

74 | Harry’s may use your IP address to help diagnose problems with its server, and to administer the Site. Your IP address is used to help identify you and to 75 | gather broad demographic information. IP addresses are also used to provide an audit trail in the case of any attempted illegal or unauthorized use of the 76 | Site. 77 |

78 |

79 | "Cookies" are pieces of information that a website transfers to your computer's hard disk for record-keeping purposes. Cookies in and of themselves do not 80 | personally identify users, although they do identify a user's computer. You may be able to set your web browser to refuse cookies. 81 |

82 |

83 | Opt-Out. 84 |

85 |

86 | If you change your mind about Harry’s use of User Data volunteered by you, you may Harry’s send an email to help@harry’s.com with your electronic mail address. 87 |

88 |

89 | Changes. 90 |

91 |

92 | Harry’s reserves the right to change or update this Privacy Policy at any time by posting a notice at the Site. Information collected by the Service is 93 | subject to the Privacy Policy in effect at the time of use. 94 |

95 |

96 | Contact. 97 |

98 |

99 | If you have any questions regarding this Privacy Policy or your dealings at this Site, please contact Harry’s info@harry’s.com. 100 |

101 | 102 |

103 | TERMS OF USE 104 |

105 |

106 | Acceptance of Terms. 107 |

108 |

109 | The following terms and conditions govern all use of the www.harrys.com website (the Site). The Site is offered subject to 110 | acceptance without modification of any of the terms and conditions contained herein or all other operating rules, policies and procedures that may be 111 | published from time to time on this Site by ADKM, Inc. (Harry’s) (collectively, the Terms of Use). 112 |

113 |

114 | If you do not agree to all of these Terms OF USE, then do not access or use the SITE. BY VIEWING THE SITE, YOU AGREE TO BE BOUND BY ALL OF THESE TERMS OF 115 | USE. 116 |

117 |

118 | Changes. 119 |

120 |

121 | Harry’s reserves the right, at its sole discretion, to modify or replace any of these Terms of Use at any time. It is your responsibility to check the 122 | Terms of Use periodically for changes. Your continued use of the Site following the posting of any changes to the Terms of Use constitutes acceptance of 123 | those changes. 124 |

125 |

126 | Information and Privacy. 127 |

128 |

129 | At your election, you may request (and Harry’s may provide) additional information about Harry’s (the Service). When you do so, 130 | you may need to submit certain information or data to Harry’s, for example, your email address. Harry’s current privacy policy is available at http:// prelaunch.harrys.com/privacy-policy (the Privacy Policy), which is incorporated 131 | by this reference. Harry’s will not edit, delete or disclose the contents of your data in connection with the Service unless (1) reasonably necessary to 132 | perform the Service, (2) authorized by you, (3) otherwise permitted under the Privacy Policy or (4) Harry’s is required to do so by law or regulation, or 133 | in the good faith belief that such action is necessary to (i) conform or comply with applicable laws, regulations or legal process, (ii) protect or defend 134 | the rights or property of Harry’s or any other user or (iii) enforce these Terms of Use. Harry’s may terminate your access to all or any part of the 135 | Service at any time, with or without cause, with or without notice, effective immediately. 136 |

137 |

138 | Rules and Conduct. 139 |

140 |

141 | As a condition of use, you promise not to use the Site for any purpose that is unlawful or prohibited by these Terms of Use, or any other purpose not 142 | reasonably intended by Harry’s. You agree to abide by all applicable local, state, national and international laws and regulations. 143 |

144 |

145 | Third Party Sites. 146 |

147 |

148 | The Site may permit you to link to other websites on the Internet, and other websites may contain links to the Site. These other websites are not under 149 | Harry’s control, and you acknowledge that Harry’s is not responsible for the accuracy, legality, appropriateness or any other aspect of the content or 150 | function of such websites. The inclusion of any such link does not imply endorsement by Harry’s or any association with its operators. 151 |

152 |

153 | Proprietary Rights. 154 |

155 |

156 | You agree that all content and materials delivered via the Site and the Service or otherwise made available by Harry’s at the Site are protected by 157 | copyrights, trademarks, service marks, patents, trade secrets or other proprietary rights and laws. Except as expressly authorized by Harry’s in writing, 158 | you agree not to sell, license, rent, modify, distribute, copy, reproduce, transmit, publicly display, publicly perform, publish, adapt, edit or create 159 | derivative works from such materials or content. However, you may print or download a reasonable number of copies of the materials or content at this Site 160 | for your internal business purposes; provided, that you retain all copyright and other proprietary notices contained therein. 161 |

162 |

163 | No Warranties. 164 |

165 |

166 | THE SITE AND ALL MATERIALS, INFORMATION, SOFTWARE, PRODUCTS AND SERVICES INCLUDED IN OR AVAILABLE THROUGH THE SITE (THE CONTENT) 167 | ARE PROVIDED "AS IS" AND "AS AVAILABLE". THE SITE, SERVICE AND CONTENT ARE PROVIDED WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING, BUT NOT 168 | LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND ANY WARRANTIES IMPLIED BY ANY 169 | COURSE OF PERFORMANCE OR USAGE OF TRADE, ALL OF WHICH ARE EXPRESSLY DISCLAIMED. HARRY’S, AND ITS AFFILIATES, LICENSORS AND SUPPLIERS DO NOT WARRANT THAT: 170 | (1) THE CONTENT IS TIMELY, ACCURATE, COMPLETE, RELIABLE OR CORRECT; (2) THE SITE WILL BE SECURE OR AVAILABLE AT ANY PARTICULAR TIME OR LOCATION; (3) ANY 171 | DEFECTS OR ERRORS WILL BE CORRECTED; (4) THE CONTENT IS FREE OF VIRUSES OR OTHER HARMFUL COMPONENTS; OR (5) THE RESULTS OF USING THE SITE OR SERVICE WILL 172 | MEET YOUR REQUIREMENTS. YOUR USE OF THE SITE AND/OR SERVICE IS SOLELY AT YOUR OWN RISK. 173 |

174 |

175 | Limitation of Liability. 176 |

177 |

178 | IN NO EVENT SHALL HARRY’S (OR ITS AFFILIATES, LICENSORS AND SUPPLIERS) BE LIABLE CONCERNING THE SUBJECT MATTER OF thESE TERMS OF USE, regardless of the 179 | form of any claim or action (whether in CONTRACT, NEGLIGENCE, STRICT LIABILITY OR OTHERwise), for any (1) MATTER BEYOND ITS REASONABLE CONTROL, (2) LOSS OR 180 | INACCURACY of data, loss or interruption OF USE, OR COST OF PROCURING SUBSTITUTE TECHNOLOGY, GOODS or SERVICES, OR (3) DIRECT OR INDIRECT, PUNITIVE, 181 | INCIDENTAL, RELIANCE, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES INCLUDING, BUT NOT LIMITED TO, LOSS OF BUSINESS, REVENUES, PROFITS OR GOODWILL, EVEN IF 182 | HARRY’S HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS ARE INDEPENDENT FROM ALL OTHER PROVISIONS OF THIS AGREEMENT AND SHALL APPLY 183 | NOTWITHSTANDING THE FAILURE OF ANY REMEDY PROVIDED HEREIN. 184 |

185 |

186 | Miscellaneous. 187 |

188 |

189 | These Terms of Use shall be governed by and construed in accordance with the laws of the State of New York, excluding its conflicts of law rules, and the 190 | United States of America. If any provision of the Terms of Use is found to be unenforceable or invalid, that provision will be limited or eliminated to the 191 | minimum extent necessary so that the Terms of Use will otherwise remain in full force and effect and enforceable. Harry’s may assign, transfer or delegate 192 | any of its rights and obligations hereunder without consent. All waivers and modifications must be in a writing signed by Harry’s, except as otherwise 193 | provided herein. No agency, partnership, joint venture, or employment relationship is created as a result of these Terms of Use, and neither party has any 194 | authority of any kind to bind the other in any respect. 195 |

196 |

197 | Copyright and Trademark Notices. 198 |

199 |

200 | Unless otherwise indicated, these Terms of Use and all Content provided by Harry’s are copyright © 2013 ADKM, Inc. All rights reserved. 201 |

202 |

203 | Harry’s and the Harry’s logo are trademarks of ADKM, Inc. 204 |

205 |

206 | Contact. 207 |

208 |

209 | You may contact Harry’s at help@harrys.com. 210 |

-------------------------------------------------------------------------------- /app/views/users/refer.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 | 8 |
9 |
10 |
11 | Shaving Is
Evolving
12 |
13 | 25 |
26 |
27 | 28 | <% 29 | referrals_count = @user.referrals.count 30 | 31 | stops = User::REFERRAL_STEPS 32 | 33 | found = nil 34 | 35 | stops.reverse_each { |stop| 36 | if stop["count"] <= referrals_count and !found 37 | stop["selected"] = true 38 | found = stop 39 | else 40 | stop["selected"] = false 41 | end 42 | } 43 | %> 44 |
<% end %>"> 45 |
46 |

Here's How It Works:

47 |
    48 |
  • Friends Joined

    Harry's Product

  • 49 | <% stops.each do |stop| %> 50 |
  • selected<% end %> <% if stop["class"] == 'five' %>last<% end %>"> 51 |
    <%= stop["count"] %>
    52 |
    53 |

    <%= stop["html"].html_safe %>

    54 | 55 |
    56 | " height="254"> 57 |
    58 |
  • 59 | <% end %> 60 |
61 | 62 |
63 |
64 |
65 |
66 |
67 | 68 | <% 69 | words = '' 70 | if referrals_count == 1 71 | words = 'friend has' 72 | else 73 | words = 'friends have' 74 | end 75 | %> 76 | 77 | <% if referrals_count == 0 %> 78 |

No friends have joined...Yet!

79 | <% else %> 80 |

<%= referrals_count %> <%= words %> joined

81 | <% end %> 82 |

Keep checking

83 | 84 |

We ship to the U.S. (& Canada Soon)

85 | Privacy Policy 86 |
87 |
88 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Prelaunchr::Application 5 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | 6 | Bundler.require(*Rails.groups) 7 | 8 | module Prelaunchr 9 | class Application < Rails::Application 10 | # Settings in config/environments/* take precedence over those specified here. 11 | # Application configuration should go into files in config/initializers 12 | # -- all .rb files in that directory are automatically loaded. 13 | 14 | # Custom directories with classes and modules you want to be autoloadable. 15 | # config.autoload_paths += %W(#{config.root}/extras) 16 | 17 | # Only load the plugins named here, in the order given (default is alphabetical). 18 | # :all can be used as a placeholder for all plugins not explicitly named. 19 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 20 | 21 | # Activate observers that should always be running. 22 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 23 | 24 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 25 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 26 | # config.time_zone = 'Central Time (US & Canada)' 27 | 28 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 29 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 30 | # config.i18n.default_locale = :de 31 | 32 | # Configure the default encoding used in templates for Ruby 1.9. 33 | config.encoding = "utf-8" 34 | 35 | # Configure sensitive parameters which will be filtered from the log file. 36 | config.filter_parameters += [:password] 37 | 38 | # Enable escaping HTML in JSON. 39 | config.active_support.escape_html_entities_in_json = true 40 | 41 | config.assets.debug = false 42 | 43 | # Use SQL instead of Active Record's schema dumper when creating the database. 44 | # This is necessary if your schema can't be completely dumped by the schema dumper, 45 | # like if you have constraints or database-specific column types 46 | # config.active_record.schema_format = :sql 47 | 48 | # Enable the asset pipeline 49 | config.assets.enabled = true 50 | 51 | # Version of your assets, change this if you want to expire all your assets 52 | config.assets.version = '1.0' 53 | 54 | # decides whether the prelaunch campaign has ended or not 55 | config.ended = ENV['CAMPAIGN_ENDED'].to_s == 'true' 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 5 | 6 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 7 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: postgresql 3 | database: prelaunchr_development 4 | host: localhost 5 | test: 6 | adapter: postgresql 7 | database: prelaunchr_test 8 | host: localhost 9 | -------------------------------------------------------------------------------- /config/database.yml.sample: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: postgresql 3 | database: prelaunchr_development 4 | host: localhost 5 | test: 6 | adapter: postgresql 7 | database: prelaunchr_test 8 | host: localhost 9 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | Prelaunchr::Application.initialize! 6 | 7 | Rails.logger = Logger.new(STDOUT) 8 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | silence_warnings do 2 | begin 3 | require 'pry' 4 | IRB = Pry 5 | rescue LoadError 6 | end 7 | end 8 | 9 | Prelaunchr::Application.configure do 10 | # Settings specified here will take precedence over those in config/application.rb 11 | config.action_mailer.delivery_method = :smtp 12 | config.action_mailer.smtp_settings = { :address => "localhost", :port => 1025 } 13 | 14 | # In the development environment your application's code is reloaded on 15 | # every request. This slows down response time but is perfect for development 16 | # since you don't have to restart the web server when you make code changes. 17 | config.cache_classes = false 18 | 19 | # Log error messages when you accidentally call methods on nil. 20 | config.whiny_nils = true 21 | 22 | config.eager_load = false 23 | 24 | # Show full error reports and disable caching 25 | config.consider_all_requests_local = true 26 | config.action_controller.perform_caching = false 27 | 28 | # Don't care if the mailer can't send 29 | config.action_mailer.raise_delivery_errors = false 30 | 31 | # Print deprecation notices to the Rails logger 32 | config.active_support.deprecation = :log 33 | 34 | # Only use best-standards-support built into browsers 35 | config.action_dispatch.best_standards_support = :builtin 36 | 37 | # Do not compress assets 38 | config.assets.compress = false 39 | 40 | # Expands the lines which load the assets 41 | config.assets.debug = true 42 | 43 | # For mailer configs 44 | config.action_mailer.perform_deliveries = true 45 | config.action_mailer.raise_delivery_errors = true 46 | config.action_mailer.default_url_options = { :host => ENV['DEFAULT_MAILER_HOST'] } 47 | end 48 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Prelaunchr::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # Code is not reloaded between requests 5 | config.cache_classes = true 6 | 7 | # Full error reports are disabled and caching is turned on 8 | config.consider_all_requests_local = false 9 | config.action_controller.perform_caching = true 10 | 11 | config.eager_load = true 12 | 13 | # Compress JavaScripts and CSS 14 | config.assets.compress = true 15 | 16 | # Don't fallback to assets pipeline if a precompiled asset is missed 17 | config.assets.compile = false 18 | 19 | # Generate digests for assets URLs 20 | config.assets.digest = true 21 | 22 | # Defaults to nil and saved in location specified by config.assets.prefix 23 | # config.assets.manifest = YOUR_PATH 24 | 25 | # Specifies the header that your server uses for sending files 26 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 27 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 28 | 29 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 30 | # config.force_ssl = true 31 | 32 | # See everything in the log (default is :info) 33 | # config.log_level = :debug 34 | 35 | # Prepend all log lines with the following tags 36 | # config.log_tags = [ :subdomain, :uuid ] 37 | 38 | # Use a different logger for distributed setups 39 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 40 | 41 | # Use a different cache store in production 42 | # config.cache_store = :mem_cache_store 43 | 44 | # Enable serving of images, stylesheets, and JavaScripts from an asset server 45 | # config.action_controller.asset_host = "http://assets.example.com" 46 | 47 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) 48 | # config.assets.precompile += %w( search.js ) 49 | 50 | # Disable delivery errors, bad email addresses will be ignored 51 | # config.action_mailer.raise_delivery_errors = false 52 | 53 | # Enable threaded mode 54 | # config.threadsafe! 55 | 56 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 57 | # the I18n.default_locale when a translation can not be found) 58 | config.i18n.fallbacks = true 59 | 60 | # Send deprecation notices to registered listeners 61 | config.active_support.deprecation = :notify 62 | 63 | config.action_mailer.default_url_options = { :host => ENV['DEFAULT_MAILER_HOST'] } 64 | 65 | # Log the query plan for queries taking more than this (works 66 | # with SQLite, MySQL, and PostgreSQL) 67 | # config.active_record.auto_explain_threshold_in_seconds = 0.5 68 | end 69 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Prelaunchr::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Configure static asset server for tests with Cache-Control for performance 11 | config.static_cache_control = "public, max-age=3600" 12 | 13 | # Log error messages when you accidentally call methods on nil 14 | config.whiny_nils = true 15 | 16 | config.eager_load = false 17 | 18 | # Show full error reports and disable caching 19 | config.consider_all_requests_local = true 20 | config.action_controller.perform_caching = false 21 | 22 | # Raise exceptions instead of rendering exception templates 23 | config.action_dispatch.show_exceptions = false 24 | 25 | # Disable request forgery protection in test environment 26 | config.action_controller.allow_forgery_protection = false 27 | 28 | # Tell Action Mailer not to deliver emails to the real world. 29 | # The :test delivery method accumulates sent emails in the 30 | # ActionMailer::Base.deliveries array. 31 | config.action_mailer.delivery_method = :test 32 | 33 | # Print deprecation notices to the stderr 34 | config.active_support.deprecation = :stderr 35 | end 36 | -------------------------------------------------------------------------------- /config/initializers/active_admin.rb: -------------------------------------------------------------------------------- 1 | ActiveAdmin.setup do |config| 2 | 3 | # == Site Title 4 | # 5 | # Set the title that is displayed on the main layout 6 | # for each of the active admin pages. 7 | # 8 | config.site_title = "Prelaunchr" 9 | 10 | # Set the link url for the title. For example, to take 11 | # users to your main site. Defaults to no link. 12 | # 13 | # config.site_title_link = "/" 14 | 15 | # Set an optional image to be displayed for the header 16 | # instead of a string (overrides :site_title) 17 | # 18 | # Note: Recommended image height is 21px to properly fit in the header 19 | # 20 | # config.site_title_image = "/images/logo.png" 21 | 22 | # == Default Namespace 23 | # 24 | # Set the default namespace each administration resource 25 | # will be added to. 26 | # 27 | # eg: 28 | # config.default_namespace = :hello_world 29 | # 30 | # This will create resources in the HelloWorld module and 31 | # will namespace routes to /hello_world/* 32 | # 33 | # To set no namespace by default, use: 34 | # config.default_namespace = false 35 | # 36 | # Default: 37 | # config.default_namespace = :admin 38 | # 39 | # You can customize the settings for each namespace by using 40 | # a namespace block. For example, to change the site title 41 | # within a namespace: 42 | # 43 | # config.namespace :admin do |admin| 44 | # admin.site_title = "Custom Admin Title" 45 | # end 46 | # 47 | # This will ONLY change the title for the admin section. Other 48 | # namespaces will continue to use the main "site_title" configuration. 49 | 50 | # == User Authentication 51 | # 52 | # Active Admin will automatically call an authentication 53 | # method in a before filter of all controller actions to 54 | # ensure that there is a currently logged in admin user. 55 | # 56 | # This setting changes the method which Active Admin calls 57 | # within the controller. 58 | config.authentication_method = :authenticate_admin_user! 59 | 60 | 61 | # == Current User 62 | # 63 | # Active Admin will associate actions with the current 64 | # user performing them. 65 | # 66 | # This setting changes the method which Active Admin calls 67 | # to return the currently logged in user. 68 | config.current_user_method = :current_admin_user 69 | 70 | 71 | # == Logging Out 72 | # 73 | # Active Admin displays a logout link on each screen. These 74 | # settings configure the location and method used for the link. 75 | # 76 | # This setting changes the path where the link points to. If it's 77 | # a string, the strings is used as the path. If it's a Symbol, we 78 | # will call the method to return the path. 79 | # 80 | # Default: 81 | config.logout_link_path = :destroy_admin_user_session_path 82 | 83 | # This setting changes the http method used when rendering the 84 | # link. For example :get, :delete, :put, etc.. 85 | # 86 | # Default: 87 | # config.logout_link_method = :get 88 | 89 | # == Root 90 | # 91 | # Set the action to call for the root path. You can set different 92 | # roots for each namespace. 93 | # 94 | # Default: 95 | # config.root_to = 'dashboard#index' 96 | 97 | # == Admin Comments 98 | # 99 | # Admin comments allow you to add comments to any model for admin use. 100 | # Admin comments are enabled by default. 101 | # 102 | # Default: 103 | # config.allow_comments = true 104 | # 105 | # You can turn them on and off for any given namespace by using a 106 | # namespace config block. 107 | # 108 | # Eg: 109 | # config.namespace :without_comments do |without_comments| 110 | # without_comments.allow_comments = false 111 | # end 112 | 113 | 114 | # == Batch Actions 115 | # 116 | # Enable and disable Batch Actions 117 | # 118 | config.batch_actions = true 119 | 120 | 121 | # == Controller Filters 122 | # 123 | # You can add before, after and around filters to all of your 124 | # Active Admin resources and pages from here. 125 | # 126 | # config.before_filter :do_something_awesome 127 | 128 | 129 | # == Register Stylesheets & Javascripts 130 | # 131 | # We recommend using the built in Active Admin layout and loading 132 | # up your own stylesheets / javascripts to customize the look 133 | # and feel. 134 | # 135 | # To load a stylesheet: 136 | # config.register_stylesheet 'my_stylesheet.css' 137 | 138 | # You can provide an options hash for more control, which is passed along to stylesheet_link_tag(): 139 | # config.register_stylesheet 'my_print_stylesheet.css', :media => :print 140 | # 141 | # To load a javascript file: 142 | # config.register_javascript 'my_javascript.js' 143 | 144 | 145 | # == CSV options 146 | # 147 | # Set the CSV builder separator (default is ",") 148 | # config.csv_column_separator = ',' 149 | # 150 | # Set the CSV builder options (default is {}) 151 | # config.csv_options = {} 152 | end 153 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | Rails.application.config.assets.precompile += %w( active_admin.css application.css ) 2 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/devise.rb: -------------------------------------------------------------------------------- 1 | # Use this hook to configure devise mailer, warden hooks and so forth. 2 | # Many of these configuration options can be set straight in your model. 3 | Devise.setup do |config| 4 | # ==> Mailer Configuration 5 | # Configure the e-mail address which will be shown in Devise::Mailer, 6 | # note that it will be overwritten if you use your own mailer class with default "from" parameter. 7 | config.mailer_sender = ENV["MAILER_SENDER"] 8 | 9 | config.secret_key = ENV["SECRET_KEY_BASE"] 10 | 11 | # Configure the class responsible to send e-mails. 12 | # config.mailer = "Devise::Mailer" 13 | 14 | # ==> ORM configuration 15 | # Load and configure the ORM. Supports :active_record (default) and 16 | # :mongoid (bson_ext recommended) by default. Other ORMs may be 17 | # available as additional gems. 18 | require 'devise/orm/active_record' 19 | 20 | # ==> Configuration for any authentication mechanism 21 | # Configure which keys are used when authenticating a user. The default is 22 | # just :email. You can configure it to use [:username, :subdomain], so for 23 | # authenticating a user, both parameters are required. Remember that those 24 | # parameters are used only when authenticating and not when retrieving from 25 | # session. If you need permissions, you should implement that in a before filter. 26 | # You can also supply a hash where the value is a boolean determining whether 27 | # or not authentication should be aborted when the value is not present. 28 | # config.authentication_keys = [ :email ] 29 | 30 | # Configure parameters from the request object used for authentication. Each entry 31 | # given should be a request method and it will automatically be passed to the 32 | # find_for_authentication method and considered in your model lookup. For instance, 33 | # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. 34 | # The same considerations mentioned for authentication_keys also apply to request_keys. 35 | # config.request_keys = [] 36 | 37 | # Configure which authentication keys should be case-insensitive. 38 | # These keys will be downcased upon creating or modifying a user and when used 39 | # to authenticate or find a user. Default is :email. 40 | config.case_insensitive_keys = [ :email ] 41 | 42 | # Configure which authentication keys should have whitespace stripped. 43 | # These keys will have whitespace before and after removed upon creating or 44 | # modifying a user and when used to authenticate or find a user. Default is :email. 45 | config.strip_whitespace_keys = [ :email ] 46 | 47 | # Tell if authentication through request.params is enabled. True by default. 48 | # It can be set to an array that will enable params authentication only for the 49 | # given strategies, for example, `config.params_authenticatable = [:database]` will 50 | # enable it only for database (email + password) authentication. 51 | # config.params_authenticatable = true 52 | 53 | # Tell if authentication through HTTP Basic Auth is enabled. False by default. 54 | # It can be set to an array that will enable http authentication only for the 55 | # given strategies, for example, `config.http_authenticatable = [:token]` will 56 | # enable it only for token authentication. 57 | # config.http_authenticatable = false 58 | 59 | # If http headers should be returned for AJAX requests. True by default. 60 | # config.http_authenticatable_on_xhr = true 61 | 62 | # The realm used in Http Basic Authentication. "Application" by default. 63 | # config.http_authentication_realm = "Application" 64 | 65 | # It will change confirmation, password recovery and other workflows 66 | # to behave the same regardless if the e-mail provided was right or wrong. 67 | # Does not affect registerable. 68 | # config.paranoid = true 69 | 70 | # By default Devise will store the user in session. You can skip storage for 71 | # :http_auth and :token_auth by adding those symbols to the array below. 72 | # Notice that if you are skipping storage for all authentication paths, you 73 | # may want to disable generating routes to Devise's sessions controller by 74 | # passing :skip => :sessions to `devise_for` in your config/routes.rb 75 | config.skip_session_storage = [:http_auth] 76 | 77 | # ==> Configuration for :database_authenticatable 78 | # For bcrypt, this is the cost for hashing the password and defaults to 10. If 79 | # using other encryptors, it sets how many times you want the password re-encrypted. 80 | # 81 | # Limiting the stretches to just one in testing will increase the performance of 82 | # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use 83 | # a value less than 10 in other environments. 84 | config.stretches = Rails.env.test? ? 1 : 10 85 | 86 | # Setup a pepper to generate the encrypted password. 87 | # config.pepper = "6ff355acc7bb0a903c92f4a6e068981cec8466f8386ac534eb6797bf60ce469cbc785c5df5312f1c813e487484de24d80cd7c8c7339455242282148aeceeca20" 88 | 89 | # ==> Configuration for :confirmable 90 | # A period that the user is allowed to access the website even without 91 | # confirming his account. For instance, if set to 2.days, the user will be 92 | # able to access the website for two days without confirming his account, 93 | # access will be blocked just in the third day. Default is 0.days, meaning 94 | # the user cannot access the website without confirming his account. 95 | # config.allow_unconfirmed_access_for = 2.days 96 | 97 | # A period that the user is allowed to confirm their account before their 98 | # token becomes invalid. For example, if set to 3.days, the user can confirm 99 | # their account within 3 days after the mail was sent, but on the fourth day 100 | # their account can't be confirmed with the token any more. 101 | # Default is nil, meaning there is no restriction on how long a user can take 102 | # before confirming their account. 103 | # config.confirm_within = 3.days 104 | 105 | # If true, requires any email changes to be confirmed (exactly the same way as 106 | # initial account confirmation) to be applied. Requires additional unconfirmed_email 107 | # db field (see migrations). Until confirmed new email is stored in 108 | # unconfirmed email column, and copied to email column on successful confirmation. 109 | config.reconfirmable = true 110 | 111 | # Defines which key will be used when confirming an account 112 | # config.confirmation_keys = [ :email ] 113 | 114 | # ==> Configuration for :rememberable 115 | # The time the user will be remembered without asking for credentials again. 116 | # config.remember_for = 2.weeks 117 | 118 | # If true, extends the user's remember period when remembered via cookie. 119 | # config.extend_remember_period = false 120 | 121 | # Options to be passed to the created cookie. For instance, you can set 122 | # :secure => true in order to force SSL only cookies. 123 | # config.rememberable_options = {} 124 | 125 | # ==> Configuration for :validatable 126 | # Range for password length. Default is 8..128. 127 | config.password_length = 8..128 128 | 129 | # Email regex used to validate email formats. It simply asserts that 130 | # an one (and only one) @ exists in the given string. This is mainly 131 | # to give user feedback and not to assert the e-mail validity. 132 | # config.email_regexp = /\A[^@]+@[^@]+\z/ 133 | 134 | # ==> Configuration for :timeoutable 135 | # The time you want to timeout the user session without activity. After this 136 | # time the user will be asked for credentials again. Default is 30 minutes. 137 | # config.timeout_in = 30.minutes 138 | 139 | # If true, expires auth token on session timeout. 140 | # config.expire_auth_token_on_timeout = false 141 | 142 | # ==> Configuration for :lockable 143 | # Defines which strategy will be used to lock an account. 144 | # :failed_attempts = Locks an account after a number of failed attempts to sign in. 145 | # :none = No lock strategy. You should handle locking by yourself. 146 | # config.lock_strategy = :failed_attempts 147 | 148 | # Defines which key will be used when locking and unlocking an account 149 | # config.unlock_keys = [ :email ] 150 | 151 | # Defines which strategy will be used to unlock an account. 152 | # :email = Sends an unlock link to the user email 153 | # :time = Re-enables login after a certain amount of time (see :unlock_in below) 154 | # :both = Enables both strategies 155 | # :none = No unlock strategy. You should handle unlocking by yourself. 156 | # config.unlock_strategy = :both 157 | 158 | # Number of authentication tries before locking an account if lock_strategy 159 | # is failed attempts. 160 | # config.maximum_attempts = 20 161 | 162 | # Time interval to unlock the account if :time is enabled as unlock_strategy. 163 | # config.unlock_in = 1.hour 164 | 165 | # ==> Configuration for :recoverable 166 | # 167 | # Defines which key will be used when recovering the password for an account 168 | # config.reset_password_keys = [ :email ] 169 | 170 | # Time interval you can reset your password with a reset password key. 171 | # Don't put a too small interval or your users won't have the time to 172 | # change their passwords. 173 | config.reset_password_within = 6.hours 174 | 175 | # ==> Configuration for :encryptable 176 | # Allow you to use another encryption algorithm besides bcrypt (default). You can use 177 | # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1, 178 | # :authlogic_sha512 (then you should set stretches above to 20 for default behavior) 179 | # and :restful_authentication_sha1 (then you should set stretches to 10, and copy 180 | # REST_AUTH_SITE_KEY to pepper) 181 | # config.encryptor = :sha512 182 | 183 | # ==> Configuration for :token_authenticatable 184 | # Defines name of the authentication token params key 185 | # config.token_authentication_key = :auth_token 186 | 187 | # ==> Scopes configuration 188 | # Turn scoped views on. Before rendering "sessions/new", it will first check for 189 | # "users/sessions/new". It's turned off by default because it's slower if you 190 | # are using only default views. 191 | # config.scoped_views = false 192 | 193 | # Configure the default scope given to Warden. By default it's the first 194 | # devise role declared in your routes (usually :user). 195 | # config.default_scope = :user 196 | 197 | # Set this configuration to false if you want /users/sign_out to sign out 198 | # only the current scope. By default, Devise signs out all scopes. 199 | # config.sign_out_all_scopes = true 200 | 201 | # ==> Navigation configuration 202 | # Lists the formats that should be treated as navigational. Formats like 203 | # :html, should redirect to the sign in page when the user does not have 204 | # access, but formats like :xml or :json, should return 401. 205 | # 206 | # If you have any extra navigational formats, like :iphone or :mobile, you 207 | # should add them to the navigational formats lists. 208 | # 209 | # The "*/*" below is required to match Internet Explorer requests. 210 | # config.navigational_formats = ["*/*", :html] 211 | 212 | # The default HTTP method used to sign out a resource. Default is :delete. 213 | config.sign_out_via = :delete 214 | 215 | # ==> OmniAuth 216 | # Add a new OmniAuth provider. Check the wiki for more information on setting 217 | # up on your models and hooks. 218 | # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo' 219 | 220 | # ==> Warden configuration 221 | # If you want to use other strategies, that are not supported by Devise, or 222 | # change the failure app, you can configure them inside the config.warden block. 223 | # 224 | # config.warden do |manager| 225 | # manager.intercept_401 = false 226 | # manager.default_strategies(:scope => :user).unshift :some_external_strategy 227 | # end 228 | 229 | # ==> Mountable engine configurations 230 | # When using Devise inside an engine, let's call it `MyEngine`, and this engine 231 | # is mountable, there are some extra configurations to be taken into account. 232 | # The following options are available, assuming the engine is mounted as: 233 | # 234 | # mount MyEngine, at: "/my_engine" 235 | # 236 | # The router that invoked `devise_for`, in the example above, would be: 237 | # config.router_name = :my_engine 238 | # 239 | # When using omniauth, Devise cannot automatically set Omniauth path, 240 | # so you need to do it manually. For the users scope, it would be: 241 | # config.omniauth_path_prefix = "/my_engine/users/auth" 242 | end 243 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | # 12 | # These inflection rules are supported but not enabled by default: 13 | # ActiveSupport::Inflector.inflections do |inflect| 14 | # inflect.acronym 'RESTful' 15 | # end 16 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Prelaunchr::Application.config.session_store :cookie_store, key: '_prelaunchr_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # Prelaunchr::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /config/initializers/strong_parameters.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Base.send(:include, ActiveModel::ForbiddenAttributesProtection) 2 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # Disable root element in JSON by default. 12 | ActiveSupport.on_load(:active_record) do 13 | self.include_root_in_json = false 14 | end 15 | -------------------------------------------------------------------------------- /config/locales/devise.en.yml: -------------------------------------------------------------------------------- 1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n 2 | 3 | en: 4 | errors: 5 | messages: 6 | expired: "has expired, please request a new one" 7 | not_found: "not found" 8 | already_confirmed: "was already confirmed, please try signing in" 9 | not_locked: "was not locked" 10 | not_saved: 11 | one: "1 error prohibited this %{resource} from being saved:" 12 | other: "%{count} errors prohibited this %{resource} from being saved:" 13 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" 14 | 15 | devise: 16 | failure: 17 | already_authenticated: 'You are already signed in.' 18 | unauthenticated: 'You need to sign in or sign up before continuing.' 19 | unconfirmed: 'You have to confirm your account before continuing.' 20 | locked: 'Your account is locked.' 21 | not_found_in_database: 'Invalid email or password.' 22 | invalid: 'Invalid email or password.' 23 | invalid_token: 'Invalid authentication token.' 24 | timeout: 'Your session expired, please sign in again to continue.' 25 | inactive: 'Your account was not activated yet.' 26 | sessions: 27 | signed_in: 'Signed in successfully.' 28 | signed_out: 'Signed out successfully.' 29 | passwords: 30 | send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.' 31 | updated: 'Your password was changed successfully. You are now signed in.' 32 | updated_not_active: 'Your password was changed successfully.' 33 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." 34 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." 35 | confirmations: 36 | send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.' 37 | send_paranoid_instructions: 'If your email address exists in our database, you will receive an email with instructions about how to confirm your account in a few minutes.' 38 | confirmed: 'Your account was successfully confirmed. You are now signed in.' 39 | registrations: 40 | signed_up: 'Welcome! You have signed up successfully.' 41 | signed_up_but_unconfirmed: 'A message with a confirmation link has been sent to your email address. Please open the link to activate your account.' 42 | signed_up_but_inactive: 'You have signed up successfully. However, we could not sign you in because your account is not yet activated.' 43 | signed_up_but_locked: 'You have signed up successfully. However, we could not sign you in because your account is locked.' 44 | updated: 'You updated your account successfully.' 45 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and click on the confirm link to finalize confirming your new email address." 46 | destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.' 47 | unlocks: 48 | send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.' 49 | unlocked: 'Your account has been unlocked successfully. Please sign in to continue.' 50 | send_paranoid_instructions: 'If your account exists, you will receive an email with instructions about how to unlock it in a few minutes.' 51 | omniauth_callbacks: 52 | success: 'Successfully authenticated from %{kind} account.' 53 | failure: 'Could not authenticate you from %{kind} because "%{reason}".' 54 | mailer: 55 | confirmation_instructions: 56 | subject: 'Confirmation instructions' 57 | reset_password_instructions: 58 | subject: 'Reset password instructions' 59 | unlock_instructions: 60 | subject: 'Unlock Instructions' 61 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Prelaunchr::Application.routes.draw do 2 | 3 | ActiveAdmin.routes(self) 4 | 5 | devise_for :admin_users, ActiveAdmin::Devise.config 6 | 7 | root :to => "users#new" 8 | 9 | post 'users/create' => 'users#create' 10 | get 'refer-a-friend' => 'users#refer' 11 | get 'privacy-policy' => 'users#policy' 12 | 13 | unless Rails.application.config.consider_all_requests_local 14 | get '*not_found', to: 'users#redirect', :format => false 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | development: 2 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 3 | 4 | test: 5 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 6 | 7 | production: 8 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 9 | -------------------------------------------------------------------------------- /config/unicorn.rb: -------------------------------------------------------------------------------- 1 | worker_processes 3 2 | timeout 30 3 | preload_app true 4 | 5 | before_fork do |server, worker| 6 | 7 | Signal.trap 'TERM' do 8 | puts 'Unicorn master intercepting TERM and sending myself QUIT instead' 9 | Process.kill 'QUIT', Process.pid 10 | end 11 | 12 | defined?(ActiveRecord::Base) and 13 | ActiveRecord::Base.connection.disconnect! 14 | end 15 | 16 | after_fork do |server, worker| 17 | 18 | Signal.trap 'TERM' do 19 | puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to sent QUIT' 20 | end 21 | 22 | defined?(ActiveRecord::Base) and 23 | ActiveRecord::Base.establish_connection 24 | end -------------------------------------------------------------------------------- /db/migrate/20130126215239_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.string :email 5 | t.string :referral_code 6 | t.integer :referrer_id 7 | 8 | t.timestamps null: false 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20130127063936_devise_create_admin_users.rb: -------------------------------------------------------------------------------- 1 | class DeviseCreateAdminUsers < ActiveRecord::Migration 2 | def migrate(direction) 3 | super 4 | # Create a default user 5 | AdminUser.create!(:email => 'tech@harrys.com', :password => 'h@rry5t3ch', :password_confirmation => 'h@rry5t3ch') if direction == :up 6 | end 7 | 8 | def change 9 | create_table(:admin_users) do |t| 10 | ## Database authenticatable 11 | t.string :email, :null => false, :default => "" 12 | t.string :encrypted_password, :null => false, :default => "" 13 | 14 | ## Recoverable 15 | t.string :reset_password_token 16 | t.datetime :reset_password_sent_at 17 | 18 | ## Rememberable 19 | t.datetime :remember_created_at 20 | 21 | ## Trackable 22 | t.integer :sign_in_count, :default => 0 23 | t.datetime :current_sign_in_at 24 | t.datetime :last_sign_in_at 25 | t.string :current_sign_in_ip 26 | t.string :last_sign_in_ip 27 | 28 | ## Confirmable 29 | # t.string :confirmation_token 30 | # t.datetime :confirmed_at 31 | # t.datetime :confirmation_sent_at 32 | # t.string :unconfirmed_email # Only if using reconfirmable 33 | 34 | ## Lockable 35 | # t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts 36 | # t.string :unlock_token # Only if unlock strategy is :email or :both 37 | # t.datetime :locked_at 38 | 39 | ## Token authenticatable 40 | # t.string :authentication_token 41 | 42 | 43 | t.timestamps null: false 44 | end 45 | 46 | add_index :admin_users, :email, :unique => true 47 | add_index :admin_users, :reset_password_token, :unique => true 48 | # add_index :admin_users, :confirmation_token, :unique => true 49 | # add_index :admin_users, :unlock_token, :unique => true 50 | # add_index :admin_users, :authentication_token, :unique => true 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /db/migrate/20130127063939_create_admin_notes.rb: -------------------------------------------------------------------------------- 1 | class CreateAdminNotes < ActiveRecord::Migration 2 | def self.up 3 | create_table :admin_notes do |t| 4 | t.string :resource_id, :null => false 5 | t.string :resource_type, :null => false 6 | t.references :admin_user, :polymorphic => true 7 | t.text :body 8 | t.timestamps null: false 9 | end 10 | add_index :admin_notes, [:resource_type, :resource_id] 11 | add_index :admin_notes, [:admin_user_type, :admin_user_id] 12 | end 13 | 14 | def self.down 15 | drop_table :admin_notes 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /db/migrate/20130127063940_move_admin_notes_to_comments.rb: -------------------------------------------------------------------------------- 1 | class MoveAdminNotesToComments < ActiveRecord::Migration 2 | def self.up 3 | remove_index :admin_notes, [:admin_user_type, :admin_user_id] 4 | rename_table :admin_notes, :active_admin_comments 5 | rename_column :active_admin_comments, :admin_user_type, :author_type 6 | rename_column :active_admin_comments, :admin_user_id, :author_id 7 | add_column :active_admin_comments, :namespace, :string 8 | add_index :active_admin_comments, [:namespace] 9 | add_index :active_admin_comments, [:author_type, :author_id] 10 | 11 | # Update all the existing comments to the default namespace 12 | say "Updating any existing comments to the #{ActiveAdmin.application.default_namespace} namespace." 13 | comments_table_name = "active_admin_comments" 14 | execute "UPDATE #{comments_table_name} SET namespace='#{ActiveAdmin.application.default_namespace}'" 15 | end 16 | 17 | def self.down 18 | remove_index :active_admin_comments, :column => [:author_type, :author_id] 19 | remove_index :active_admin_comments, :column => [:namespace] 20 | remove_column :active_admin_comments, :namespace 21 | rename_column :active_admin_comments, :author_id, :admin_user_id 22 | rename_column :active_admin_comments, :author_type, :admin_user_type 23 | rename_table :active_admin_comments, :admin_notes 24 | add_index :admin_notes, [:admin_user_type, :admin_user_id] 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /db/migrate/20130227185712_create_delayed_jobs.rb: -------------------------------------------------------------------------------- 1 | class CreateDelayedJobs < ActiveRecord::Migration 2 | def self.up 3 | create_table :delayed_jobs, :force => true do |table| 4 | table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue 5 | table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually. 6 | table.text :handler # YAML-encoded string of the object that will do work 7 | table.text :last_error # reason for last failure (See Note below) 8 | table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future. 9 | table.datetime :locked_at # Set when a client is working on this object 10 | table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead) 11 | table.string :locked_by # Who is working on this object (if locked) 12 | table.string :queue # The name of the queue this job is in 13 | table.timestamps null: false 14 | end 15 | 16 | add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority' 17 | end 18 | 19 | def self.down 20 | drop_table :delayed_jobs 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /db/migrate/20130312045541_create_ip_addresses.rb: -------------------------------------------------------------------------------- 1 | class CreateIpAddresses < ActiveRecord::Migration 2 | def change 3 | create_table :ip_addresses do |t| 4 | t.string :address 5 | t.integer :count 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended that you check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20130312045541) do 15 | 16 | # These are extensions that must be enabled in order to support this database 17 | enable_extension "plpgsql" 18 | 19 | create_table "active_admin_comments", force: :cascade do |t| 20 | t.string "resource_id", null: false 21 | t.string "resource_type", null: false 22 | t.integer "author_id" 23 | t.string "author_type" 24 | t.text "body" 25 | t.datetime "created_at", null: false 26 | t.datetime "updated_at", null: false 27 | t.string "namespace" 28 | end 29 | 30 | add_index "active_admin_comments", ["author_type", "author_id"], name: "index_active_admin_comments_on_author_type_and_author_id", using: :btree 31 | add_index "active_admin_comments", ["namespace"], name: "index_active_admin_comments_on_namespace", using: :btree 32 | add_index "active_admin_comments", ["resource_type", "resource_id"], name: "index_active_admin_comments_on_resource_type_and_resource_id", using: :btree 33 | 34 | create_table "admin_users", force: :cascade do |t| 35 | t.string "email", default: "", null: false 36 | t.string "encrypted_password", default: "", null: false 37 | t.string "reset_password_token" 38 | t.datetime "reset_password_sent_at" 39 | t.datetime "remember_created_at" 40 | t.integer "sign_in_count", default: 0 41 | t.datetime "current_sign_in_at" 42 | t.datetime "last_sign_in_at" 43 | t.string "current_sign_in_ip" 44 | t.string "last_sign_in_ip" 45 | t.datetime "created_at", null: false 46 | t.datetime "updated_at", null: false 47 | end 48 | 49 | add_index "admin_users", ["email"], name: "index_admin_users_on_email", unique: true, using: :btree 50 | add_index "admin_users", ["reset_password_token"], name: "index_admin_users_on_reset_password_token", unique: true, using: :btree 51 | 52 | create_table "delayed_jobs", force: :cascade do |t| 53 | t.integer "priority", default: 0 54 | t.integer "attempts", default: 0 55 | t.text "handler" 56 | t.text "last_error" 57 | t.datetime "run_at" 58 | t.datetime "locked_at" 59 | t.datetime "failed_at" 60 | t.string "locked_by" 61 | t.string "queue" 62 | t.datetime "created_at", null: false 63 | t.datetime "updated_at", null: false 64 | end 65 | 66 | add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree 67 | 68 | create_table "ip_addresses", force: :cascade do |t| 69 | t.string "address" 70 | t.integer "count" 71 | t.datetime "created_at", null: false 72 | t.datetime "updated_at", null: false 73 | end 74 | 75 | create_table "users", force: :cascade do |t| 76 | t.string "email" 77 | t.string "referral_code" 78 | t.integer "referrer_id" 79 | t.datetime "created_at", null: false 80 | t.datetime "updated_at", null: false 81 | end 82 | 83 | end 84 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | # AdminUser.create!(:email => 'admin@prelaunchr.com', :password => 'changeme', :password_confirmation => 'changeme') 9 | -------------------------------------------------------------------------------- /doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /lib/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/lib/assets/.gitkeep -------------------------------------------------------------------------------- /lib/tasks/prelaunchr.rake: -------------------------------------------------------------------------------- 1 | require 'csv' 2 | 3 | namespace :prelaunchr do 4 | desc "Will out put CSV's for each group of users you should email" 5 | task :create_winner_csvs => :environment do 6 | stops = User::REFERRAL_STEPS.map{|stop| stop["count"]} 7 | 8 | winners = Hash.new {|h,k| h[k]=[]} 9 | User.all.each { |user| 10 | found = nil 11 | 12 | stops.reverse_each { |stop| 13 | if stop <= user.referrals.count and !found 14 | found = stop 15 | end 16 | } 17 | 18 | if found 19 | winners[found] << user 20 | end 21 | } 22 | 23 | winners.each { |stop, list| 24 | CSV.open("#{Rails.root}/lib/assets/group_#{stop}.csv", "wb") do |csv| 25 | list.each { |user| 26 | csv << [user.email, user.referrals.count] 27 | } 28 | end 29 | } 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/log/.gitkeep -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /script/delayed_job: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment')) 4 | require 'delayed/command' 5 | Delayed::Command.new(ARGV).daemonize 6 | -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /spec/controllers/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require 'securerandom' 3 | 4 | def generate_email 5 | "#{SecureRandom.hex}@example.com" 6 | end 7 | 8 | describe UsersController, type: :controller do 9 | before do 10 | allow(Rails.application.config).to receive(:ended) { false } 11 | end 12 | 13 | describe 'new' do 14 | before(:each) do 15 | @referral_code = SecureRandom.hex(5) 16 | end 17 | 18 | it 'renders new user/landing page on first visit' do 19 | get :new 20 | expect(response).to have_http_status(:success) 21 | end 22 | 23 | it 'skips first page on saved user\'s second visit' do 24 | cookies[:h_email] = generate_email 25 | expect(User).to receive(:find_by_email).with(cookies[:h_email]) do 26 | User.new 27 | end 28 | 29 | get :new 30 | expect(response).to redirect_to '/refer-a-friend' 31 | end 32 | 33 | it 'assigns referral code to cookie if code belongs to user' do 34 | expect(User).to receive(:find_by_referral_code).with(@referral_code) do 35 | User.new 36 | end 37 | 38 | get :new, ref: @referral_code 39 | expect(cookies[:h_ref]).to eq(@referral_code) 40 | end 41 | 42 | it 'continues request when user agent is facebookexternalhit/1.1' do 43 | request.env['HTTP_USER_AGENT'] = 'facebookexternalhit/1.1' 44 | 45 | get :new, ref: @referral_code 46 | expect(response).to render_template :new 47 | end 48 | 49 | it 'redirects to intended url when user agent is not facebookexternalhit/1.1' do 50 | request.env['HTTP_USER_AGENT'] = 'notfacebookexternalhit' 51 | 52 | get :new, ref: @referral_code 53 | expect(response).to redirect_to root_path 54 | end 55 | end 56 | 57 | describe 'refer' do 58 | it 'should redirect to landing page if there is no email in cookie' do 59 | get :refer 60 | expect(response).to redirect_to root_path 61 | end 62 | 63 | it 'should render /refer-a-friend page if known email exists in cookie' do 64 | cookies[:h_email] = generate_email 65 | expect(User).to receive(:find_by_email) { User.new } 66 | 67 | get :refer 68 | expect(response).to render_template('refer') 69 | end 70 | end 71 | 72 | describe 'saving users' do 73 | before(:each) do 74 | @email = generate_email 75 | end 76 | 77 | it 'redirects to /refer-a-friend on creation' do 78 | post :create, user: { email: @email } 79 | expect(response).to redirect_to '/refer-a-friend' 80 | end 81 | 82 | it 'has a referrer when referred' do 83 | referrer_email = "#{SecureRandom.hex}@example.com" 84 | referrer = User.create(email: referrer_email) 85 | cookies[:h_ref] = referrer.referral_code 86 | 87 | post :create, user: { email: @email } 88 | user = assigns(:user) 89 | expect(user.referrer.email).to eq(referrer.email) 90 | end 91 | 92 | it 'does not have a referrer when signing up unreferred' do 93 | post :create, user: { email: @email } 94 | user = assigns(:user) 95 | expect(user.referrer).to be_nil 96 | end 97 | 98 | context 'ip addresses' do 99 | before(:each) do 100 | @ip_address = "192.0.2.#{SecureRandom.hex(3)}" 101 | request.env['HTTP_X_FORWARDED_FOR'] = @ip_address 102 | post :create, user: { email: @email } 103 | @saved_ip = IpAddress.find_by_address @ip_address 104 | end 105 | 106 | it 'creates a new IpAddress on new email submission' do 107 | expect(@saved_ip).to_not be_nil 108 | expect(@saved_ip.count).to eq(1) 109 | expect(@saved_ip.address).to eq(@ip_address) 110 | end 111 | 112 | it 'increases the count of the IpAddress when then address appears again with same email' do 113 | post :create, user: { email: @email } 114 | 115 | updated_ip = IpAddress.find_by_address @ip_address 116 | expect(updated_ip).to_not be_nil 117 | expect(updated_ip.count).to eq(2) 118 | end 119 | 120 | it 'increases the count of the IpAddress when then address appears again with different email' do 121 | post :create, user: { email: generate_email } 122 | 123 | updated_ip = IpAddress.find_by_address @ip_address 124 | expect(updated_ip.count).to eq(2) 125 | end 126 | 127 | it 'redirects to /refer-a-friend if the ip count is less than 3' do 128 | post :create, user: { email: generate_email } 129 | expect(response).to redirect_to '/refer-a-friend' 130 | end 131 | 132 | it 'redirects to landing page when ip has already appeared 3 times' do 133 | post :create, user: { email: generate_email } 134 | post :create, user: { email: generate_email } 135 | post :create, user: { email: generate_email } # 4th time 136 | 137 | expect(response).to redirect_to root_path 138 | end 139 | 140 | it 'redirects to landing page when resubmitting from different ips' do 141 | new_address = "192.0.2.#{SecureRandom.hex(3)}" 142 | request.env['HTTP_X_FORWARDED_FOR'] = new_address 143 | post :create, user: { email: @email } 144 | expect(response).to redirect_to('/') 145 | end 146 | end 147 | 148 | # probably should do more than redirect, but this is current behavior 149 | it 'redirects to main page when malformed email is submitted' do 150 | post :create, user: { email: 'notanemail' } 151 | expect(response).to redirect_to root_path 152 | end 153 | end 154 | end 155 | -------------------------------------------------------------------------------- /spec/integration/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "users", type: :request do 4 | describe 'campaign ended' do 5 | before do 6 | allow(Rails.application.config).to receive(:ended) { true } 7 | end 8 | 9 | it 'should display campaign ended message when campaign has ended' do 10 | get '/' 11 | expect(response).to render_template(:partial => "_campaign_ended") 12 | end 13 | end 14 | end -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require 'securerandom' 3 | 4 | RSpec.describe User, type: :model do 5 | it 'should error when saving without email' do 6 | expect do 7 | user = User.new 8 | user.save! 9 | end.to raise_error(ActiveRecord::RecordInvalid) 10 | end 11 | 12 | it 'should error when saving with malformed email' do 13 | expect do 14 | User.create!(email: 'notanemail') 15 | end.to raise_error(ActiveRecord::RecordInvalid) 16 | end 17 | 18 | it 'should generate a referral code on creation' do 19 | user = User.new(email: 'user@example.com') 20 | user.run_callbacks(:create) 21 | expect(user).to receive(:create_referral_code) 22 | user.save! 23 | expect(user.referral_code).to_not be_nil 24 | end 25 | 26 | it 'should send a welcome email on save' do 27 | user = User.new(email: 'user@example.com') 28 | user.run_callbacks(:create) 29 | expect(user).to receive(:send_welcome_email) 30 | user.save! 31 | end 32 | end 33 | 34 | RSpec.describe UsersHelper do 35 | describe 'unused_referral_code' do 36 | it 'should return the same referral code if there is no collision' do 37 | expect(User).to receive(:find_by_referral_code).and_return(nil) 38 | UsersHelper.unused_referral_code 39 | end 40 | 41 | it 'should return a new referral code if there is a collision' do 42 | expect(User).to receive(:find_by_referral_code) 43 | .exactly(2).times.and_return('collision', nil) 44 | UsersHelper.unused_referral_code 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | ENV['RAILS_ENV'] ||= 'test' 3 | require File.expand_path('../../config/environment', __FILE__) 4 | # Prevent database truncation if the environment is production 5 | abort("The Rails environment is running in production mode!") if Rails.env.production? 6 | require 'spec_helper' 7 | require 'rspec/rails' 8 | # Add additional requires below this line. Rails is not loaded until this point! 9 | 10 | # Requires supporting ruby files with custom matchers and macros, etc, in 11 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 12 | # run as spec files by default. This means that files in spec/support that end 13 | # in _spec.rb will both be required and run as specs, causing the specs to be 14 | # run twice. It is recommended that you do not name files matching this glob to 15 | # end with _spec.rb. You can configure this pattern with the --pattern 16 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 17 | # 18 | # The following line is provided for convenience purposes. It has the downside 19 | # of increasing the boot-up time by auto-requiring all files in the support 20 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 21 | # require only the support files necessary. 22 | # 23 | # Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } 24 | 25 | # Checks for pending migration and applies them before tests are run. 26 | # If you are not using ActiveRecord, you can remove this line. 27 | 28 | ActiveRecord::Migration.maintain_test_schema! 29 | 30 | RSpec.configure do |config| 31 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 32 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 33 | 34 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 35 | # examples within a transaction, remove the following line or assign false 36 | # instead of true. 37 | config.use_transactional_fixtures = true 38 | 39 | # RSpec Rails can automatically mix in different behaviours to your tests 40 | # based on their file location, for example enabling you to call `get` and 41 | # `post` in specs under `spec/controllers`. 42 | # 43 | # You can disable this behaviour by removing the line below, and instead 44 | # explicitly tag your specs with their type, e.g.: 45 | # 46 | # RSpec.describe UsersController, :type => :controller do 47 | # # ... 48 | # end 49 | # 50 | # The different available types are documented in the features, such as in 51 | # https://relishapp.com/rspec/rspec-rails/docs 52 | # config.infer_spec_type_from_file_location! 53 | 54 | # Filter lines from Rails gems in backtraces. 55 | config.filter_rails_from_backtrace! 56 | # arbitrary gems may also be filtered via: 57 | # config.filter_gems_from_backtrace("gem name") 58 | end 59 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # The generated `.rspec` file contains `--require spec_helper` which will cause 4 | # this file to always be loaded, without a need to explicitly require it in any 5 | # files. 6 | # 7 | # Given that it is always loaded, you are encouraged to keep this file as 8 | # light-weight as possible. Requiring heavyweight dependencies from this file 9 | # will add to the boot time of your test suite on EVERY test run, even for an 10 | # individual file that may not need all of that loaded. Instead, consider making 11 | # a separate helper file that requires the additional dependencies and performs 12 | # the additional setup, and require it from the spec files that actually need 13 | # it. 14 | # 15 | # The `.rspec` file also contains a few flags that are not defaults but that 16 | # users commonly want. 17 | # 18 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 19 | RSpec.configure do |config| 20 | # rspec-expectations config goes here. You can use an alternate 21 | # assertion/expectation library such as wrong or the stdlib/minitest 22 | # assertions if you prefer. 23 | config.expect_with :rspec do |expectations| 24 | # This option will default to `true` in RSpec 4. It makes the `description` 25 | # and `failure_message` of custom matchers include text for helper methods 26 | # defined using `chain`, e.g.: 27 | # be_bigger_than(2).and_smaller_than(4).description 28 | # # => "be bigger than 2 and smaller than 4" 29 | # ...rather than: 30 | # # => "be bigger than 2" 31 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 32 | end 33 | 34 | # rspec-mocks config goes here. You can use an alternate test double 35 | # library (such as bogus or mocha) by changing the `mock_with` option here. 36 | config.mock_with :rspec do |mocks| 37 | # Prevents you from mocking or stubbing a method that does not exist on 38 | # a real object. This is generally recommended, and will default to 39 | # `true` in RSpec 4. 40 | mocks.verify_partial_doubles = true 41 | end 42 | 43 | # The settings below are suggested to provide a good initial experience 44 | # with RSpec, but feel free to customize to your heart's content. 45 | 46 | # These two settings work together to allow you to limit a spec run 47 | # to individual examples or groups you care about by tagging them with 48 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples 49 | # get run. 50 | config.filter_run :focus 51 | config.run_all_when_everything_filtered = true 52 | 53 | # Allows RSpec to persist some state between runs in order to support 54 | # the `--only-failures` and `--next-failure` CLI options. We recommend 55 | # you configure your source control system to ignore this file. 56 | # config.example_status_persistence_file_path = "spec/examples.txt" 57 | 58 | # Limits the available syntax to the non-monkey patched syntax that is 59 | # recommended. For more details, see: 60 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 61 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 62 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 63 | # config.disable_monkey_patching! 64 | 65 | # Many RSpec users commonly either run the entire suite or an individual 66 | # file, and it's useful to allow more verbose output when running an 67 | # individual spec file. 68 | if config.files_to_run.one? 69 | # Use the documentation formatter for detailed output, 70 | # unless a formatter has already been configured 71 | # (e.g. via a command-line flag). 72 | config.default_formatter = 'doc' 73 | end 74 | 75 | # Print the 3 slowest examples and example groups at the 76 | # end of the spec run, to help surface which specs are running 77 | # particularly slow. 78 | config.profile_examples = 3 79 | 80 | # Run specs in random order to surface order dependencies. If you find an 81 | # order dependency and want to debug it, you can fix the order by providing 82 | # the seed, which is printed after each run. 83 | # --seed 1234 84 | config.order = :random 85 | 86 | # Seed global randomization in this process using the `--seed` CLI option. 87 | # Setting this allows you to use `--seed` to deterministically reproduce 88 | # test failures related to randomization by passing the same `--seed` value 89 | # as the one that triggered the failure. 90 | Kernel.srand config.seed 91 | 92 | end 93 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/vendor/assets/javascripts/.gitkeep -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/vendor/assets/stylesheets/.gitkeep -------------------------------------------------------------------------------- /vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrystech/prelaunchr/e62df4537e2938256fa958aa063547a8fd35d23d/vendor/plugins/.gitkeep --------------------------------------------------------------------------------