Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports and disable caching.
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send.
17 | config.action_mailer.raise_delivery_errors = false
18 | config.action_mailer.default_url_options = {host: 'localhost:3000'}
19 |
20 | # Print deprecation notices to the Rails logger.
21 | config.active_support.deprecation = :log
22 |
23 | # Raise an error on page load if there are pending migrations.
24 | config.active_record.migration_error = :page_load
25 |
26 | # Debug mode disables concatenation and preprocessing of assets.
27 | # This option may cause significant delays in view rendering with a large
28 | # number of complex assets.
29 | config.assets.debug = true
30 |
31 | # Asset digests allow you to set far-future HTTP expiration dates on all assets,
32 | # yet still be able to expire them through the digest params.
33 | config.assets.digest = true
34 |
35 | # Adds additional error checking when serving assets at runtime.
36 | # Checks for improperly declared sprockets dependencies.
37 | # Raises helpful error messages.
38 | config.assets.raise_runtime_errors = true
39 |
40 | # Raises error for missing translations
41 | # config.action_view.raise_on_missing_translations = true
42 | end
43 |
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/controllers/users_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class UsersControllerTest < ActionController::TestCase
4 | include Devise::TestHelpers
5 | setup do
6 | request.env['devise.mapping'] = Devise.mappings[:user]
7 | @user = users(:erik)
8 | end
9 |
10 | test 'should render edit form' do
11 | sign_in @user
12 | get :edit
13 | assert_response :success
14 | assert_select 'form#edit_form' do
15 | assert_select '[action=?]', '/users'
16 | assert_select '[method=?]', 'post'
17 | end
18 | assert_select 'input', count: 15
19 | assert_select 'label', count: 12
20 | assert_select 'input[name="commit"]' do
21 | assert_select '[type=?]', 'submit'
22 | assert_select '[value=?]', 'Update'
23 | end
24 | assert_select 'a.btn', 'Back'
25 | end
26 |
27 | test 'should update user if password is correct' do
28 | sign_in @user
29 | assert_not_equal 'New Name', @user.name
30 | put :update, user: {name: 'New Name', current_password: 'correct'}
31 | @user.reload
32 | assert_equal 'New Name', @user.name
33 | assert_response :redirect
34 | assert_redirected_to controller: 'sidebar', action: 'search'
35 | end
36 |
37 | test 'should return error if password is incorrect' do
38 | sign_in @user
39 | put :update, user: {name: 'New Name', current_password: 'incorrect'}
40 | assert_response :error
41 | end
42 |
43 | test 'should create user if information is valid' do
44 | post :create, user: {email: 'user@example.com', name: 'User', password: 'correct', password_confirmation: 'correct'}
45 | assert_response :success
46 | end
47 |
48 | test 'should return error if information is invalid' do
49 | post :create, user: {email: 'user@example.com', name: 'User', password: 'correct', password_confirmation: 'incorrect'}
50 | assert_response :error
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/db/migrate/00000000000002_add_devise_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddDeviseToUsers < ActiveRecord::Migration
2 | def up
3 | change_table(:users) do |t|
4 | ## Database authenticatable
5 | # t.string :email, null: false, default: ""
6 | t.string :encrypted_password, null: false, default: ''
7 |
8 | ## Recoverable
9 | t.string :reset_password_token
10 | t.datetime :reset_password_sent_at
11 |
12 | ## Rememberable
13 | t.datetime :remember_created_at
14 |
15 | ## Trackable
16 | t.integer :sign_in_count, default: 0, null: false
17 | t.datetime :current_sign_in_at
18 | t.datetime :last_sign_in_at
19 | t.string :current_sign_in_ip
20 | t.string :last_sign_in_ip
21 |
22 | ## Confirmable
23 | # t.string :confirmation_token
24 | # t.datetime :confirmed_at
25 | # t.datetime :confirmation_sent_at
26 | # t.string :unconfirmed_email # Only if using reconfirmable
27 |
28 | ## Lockable
29 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
30 | # t.string :unlock_token # Only if unlock strategy is :email or :both
31 | # t.datetime :locked_at
32 |
33 | # Uncomment below if timestamps were not included in your original model.
34 | # t.timestamps
35 | end
36 |
37 | # add_index :users, :email, unique: true
38 | add_index :users, :reset_password_token, unique: true
39 | # add_index :users, :confirmation_token, unique: true
40 | # add_index :users, :unlock_token, unique: true
41 | end
42 |
43 | def down
44 | # By default, we don't want to make any assumption about how to roll back a migration when your
45 | # model already existed. Please edit below which fields you would like to remove in this migration.
46 | raise ActiveRecord::IrreversibleMigration
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/test/controllers/passwords_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class PasswordsControllerTest < ActionController::TestCase
4 | include Devise::TestHelpers
5 | setup do
6 | request.env['devise.mapping'] = Devise.mappings[:user]
7 | @user = users(:erik)
8 | end
9 |
10 | test 'should send password reset instructions if email address is found' do
11 | num_deliveries = ActionMailer::Base.deliveries.size
12 | post :create, user: {email: @user.email}
13 | assert_equal num_deliveries + 1, ActionMailer::Base.deliveries.size
14 | assert_response :success
15 | email = ActionMailer::Base.deliveries.last
16 | assert_equal [@user.email], email.to
17 | assert_equal 'Reset password instructions', email.subject
18 | end
19 |
20 | test 'should not send password reset instructions if email address is not found' do
21 | post :create, user: {email: 'not_found@example.com'}
22 | assert_response :error
23 | end
24 |
25 | test 'should render edit view' do
26 | get :edit, reset_password_token: 'token'
27 | assert_response :success
28 | end
29 |
30 | test 'should reset user password with an valid reset password token' do
31 | token = @user.send_reset_password_instructions
32 | put :update, user: {reset_password_token: token, password: 'new_password'}
33 | @user.reload
34 | assert @user.valid_password?('new_password')
35 | assert_response :redirect
36 | assert_redirected_to controller: 'main', action: 'index'
37 | end
38 |
39 | test 'should not reset user password with an invalid reset password token' do
40 | @user.send_reset_password_instructions
41 | put :update, user: {reset_password_token: 'invalid_token', password: 'new_password'}
42 | @user.reload
43 | assert !@user.valid_password?('new_password')
44 | assert_response :redirect
45 | assert_redirected_to controller: 'main', action: 'index'
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.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 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure static file server for tests with Cache-Control for performance.
16 | config.serve_static_files = true
17 | config.static_cache_control = 'public, max-age=3600'
18 |
19 | # Show full error reports and disable caching.
20 | config.consider_all_requests_local = true
21 | config.action_controller.perform_caching = false
22 |
23 | config.log_level = :warn
24 |
25 | # Raise exceptions instead of rendering exception templates.
26 | config.action_dispatch.show_exceptions = false
27 |
28 | # Disable request forgery protection in test environment.
29 | config.action_controller.allow_forgery_protection = false
30 |
31 | # Tell Action Mailer not to deliver emails to the real world.
32 | # The :test delivery method accumulates sent emails in the
33 | # ActionMailer::Base.deliveries array.
34 | config.action_mailer.delivery_method = :test
35 | config.action_mailer.default_url_options = {host: 'localhost:3000'}
36 |
37 | # Randomize the order test cases are executed.
38 | config.active_support.test_order = :random
39 |
40 | # Print deprecation notices to the stderr.
41 | config.active_support.deprecation = :stderr
42 |
43 | # Raises error for missing translations
44 | # config.action_view.raise_on_missing_translations = true
45 | end
46 |
--------------------------------------------------------------------------------
/test/controllers/info_window_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class InfoWindowControllerTest < ActionController::TestCase
4 | include Devise::TestHelpers
5 | setup do
6 | @thing = things(:thing_1)
7 | @user = users(:erik)
8 | end
9 |
10 | test 'should thank the user if the hydrant is adopted by the user' do
11 | sign_in @user
12 | @thing.user_id = @user.id
13 | @thing.save!
14 | get :index, thing_id: @thing.id
15 | assert_not_nil assigns :thing
16 | assert_response :success
17 | assert_template 'users/thank_you'
18 | assert_select 'h2', 'Thank you for adopting this hydrant!'
19 | assert_select 'form#abandon_form' do
20 | assert_select '[action=?]', '/things'
21 | assert_select '[method=?]', 'post'
22 | end
23 | assert_select 'input[name="_method"]' do
24 | assert_select '[type=?]', 'hidden'
25 | assert_select '[value=?]', 'put'
26 | end
27 | assert_select 'input[name="commit"]' do
28 | assert_select '[type=?]', 'submit'
29 | assert_select '[value=?]', 'Abandon this hydrant'
30 | end
31 | end
32 |
33 | test 'should show the profile if the hydrant is adopted' do
34 | @thing.user_id = @user.id
35 | @thing.save!
36 | get :index, thing_id: @thing.id
37 | assert_not_nil assigns :thing
38 | assert_response :success
39 | assert_template 'users/profile'
40 | assert_select 'h2', /This hydrant has been adopted\s+by #{@user.name}\s+of #{@user.organization}/
41 | end
42 |
43 | test 'should show adoption form if hydrant is not adopted' do
44 | sign_in @user
45 | get :index, thing_id: @thing.id
46 | assert_not_nil assigns :thing
47 | assert_response :success
48 | assert_template :adopt
49 | assert_select 'h2', 'Adopt this Hydrant'
50 | assert_select 'form#adoption_form' do
51 | assert_select '[action=?]', '/things'
52 | assert_select '[method=?]', 'post'
53 | end
54 | assert_select 'input[name="_method"]' do
55 | assert_select '[type=?]', 'hidden'
56 | assert_select '[value=?]', 'put'
57 | end
58 | assert_select 'input[name="commit"]' do
59 | assert_select '[type=?]', 'submit'
60 | assert_select '[value=?]', 'Adopt!'
61 | end
62 | end
63 |
64 | test 'should show sign-in form if signed out' do
65 | get :index, thing_id: @thing.id
66 | assert_not_nil assigns :thing
67 | assert_response :success
68 | assert_template 'users/sign_in'
69 | assert_select 'h2', 'Sign in to adopt this Hydrant'
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Please maintain alphabetical order
2 |
3 | en:
4 | buttons:
5 | abandon: "Abandon this %{thing}"
6 | adopt: "Adopt!"
7 | back: "Back"
8 | change_password: "Change my password"
9 | close: "Close"
10 | edit_profile: "Edit profile"
11 | email_password: "Email me my password"
12 | find: "Find %{thing}"
13 | send_reminder: "Send reminder to shovel"
14 | sign_in: "Sign in"
15 | sign_out: "Sign out"
16 | sign_up: "Sign up"
17 | update: "Update"
18 | captions:
19 | optional: "(optional)"
20 | private: "(private)"
21 | public: "(visible to others)"
22 | required: "(required)"
23 | defaults:
24 | address: "address"
25 | address_1: "1 City Hall Plaza"
26 | address_2: "Suite 500"
27 | city: "Boston"
28 | city_state: "Boston, Massachusetts"
29 | neighborhood: "Downtown"
30 | sms_number: "857-555-1212"
31 | state: "MA"
32 | thing: "hydrant"
33 | this_thing: "This %{thing}"
34 | tagline: "Claim responsibility for shoveling out a fire hydrant after it snows."
35 | tos: "By signing up, you agree to the %{tos}."
36 | voice_number: "617-555-1212"
37 | zip: "02201-2013"
38 | errors:
39 | password: "You need to sign in or sign up before continuing."
40 | not_found: "Could not find %{thing}."
41 | labels:
42 | address: "Address, Neighborhood"
43 | address_1: "Address Line 1"
44 | address_2: "Address Line 2"
45 | city: "City"
46 | city_state: "City"
47 | email: "Email address"
48 | name: "Name"
49 | name_thing: "Give this %{thing} a name"
50 | organization: "Organization"
51 | password: "Password"
52 | password_choose: "Choose a password"
53 | password_current: "Current password"
54 | password_new: "New password"
55 | remember_me: "Stay signed in"
56 | sms_number: "Mobile phone number"
57 | state: "State"
58 | user_existing: "I've already signed up"
59 | user_new: "I haven't signed up yet"
60 | voice_number: "Home phone number"
61 | zip: "ZIP"
62 | links:
63 | feedback: "Send feedback"
64 | forgot_password: "Forgot your password?"
65 | remembered_password: "Never mind. I remembered my password."
66 | notices:
67 | abandoned: "%{thing} abandoned!"
68 | adopted: "You just adopted a %{thing}!"
69 | password_reset: "Password reset instructions sent! Check your email."
70 | reminder_sent: "Reminder sent!"
71 | signed_in: "Signed in!"
72 | signed_out: "Signed out."
73 | signed_up: "Thanks for signing up!"
74 | stolen: "%{thing} stolen!"
75 | titles:
76 | adopt: "Adopt this %{thing}"
77 | adopted: "%{thing_name} has been adopted"
78 | byline: "by %{name}"
79 | edit_profile: "Edit your Profile"
80 | main: "Adopt-a-%{thing}"
81 | ofline: "of %{organization}"
82 | sign_in: "Sign in to adopt this %{thing}"
83 | thank_you: "Thank you for adopting this %{thing}!"
84 | tos: "Terms of Service"
85 | sponsors:
86 | built: "Built in Boston"
87 | cfa: "Code for America"
88 | city: "City of Boston"
89 | mayor:
90 | name: "Martin J. Walsh"
91 | title: "Mayor"
92 |
--------------------------------------------------------------------------------
/config/locales/pt.yml:
--------------------------------------------------------------------------------
1 | # Please maintain alphabetical order
2 |
3 | pt:
4 | buttons:
5 | abandon: "Abandonar esse %{thing}"
6 | adopt: "Adote!"
7 | back: "De volta"
8 | change_password: "Alterar a minha senha"
9 | close: "Fechar"
10 | edit_profile: "Editar perfil"
11 | email_password: "E-mail minha senha"
12 | find: "Encontrar %{thing}"
13 | send_reminder: "Enviar lembrete para pá"
14 | sign_in: "Entrar"
15 | sign_out: "Sair"
16 | sign_up: "Inscrever-se"
17 | update: "Atualizar"
18 | captions:
19 | optional: "(opcional)"
20 | private: "(privado)"
21 | public: "(visível para os outros)"
22 | required: "(exigido)"
23 | defaults:
24 | address: "endereço"
25 | address_1: "1 City Hall Plaza"
26 | address_2: "Suite 500"
27 | city: "Boston"
28 | city_state: "Boston, Massachusetts"
29 | neighborhood: "Downtown"
30 | sms_number: "857-555-1212"
31 | state: "MA"
32 | thing: "hidrante"
33 | this_thing: "Este %{thing}"
34 | tagline: "Responsabilidade pedido de pá para fora um %{thing} de incêndio depois que neva."
35 | tos: "Ao inscrever-se, você concorda com os %{tos}."
36 | voice_number: "617-555-1212"
37 | zip: "02201-2013"
38 | errors:
39 | password: "Você precisa fazer login ou inscreva-se antes de continuar."
40 | not_found: "Não foi possível encontrar %{thing}."
41 | labels:
42 | address: "Endereço, Bairro"
43 | address_1: "Endereço linha 1"
44 | address_2: "Endereço linha 2"
45 | city: "Cidade"
46 | city_state: "Cidade"
47 | email: "Endereço de email"
48 | name: "Nome"
49 | name_thing: "Dê este %{thing} um nome"
50 | organization: "Organização"
51 | password: "Senha"
52 | password_choose: "Escolha uma senha"
53 | password_current: "Senha atual"
54 | password_new: "Nova senha"
55 | remember_me: "Fique assinado em"
56 | sms_number: "Número de telemóvel"
57 | state: "Estado"
58 | user_existing: "Eu já se inscreveram"
59 | user_new: "Eu não se inscreveram ainda"
60 | voice_number: "Número de telefone residencial"
61 | zip: "Código postal"
62 | links:
63 | feedback: "Envie seu comentário"
64 | forgot_password: "Esqueceu sua senha?"
65 | remembered_password: "Não se preocupe. Lembrei-me minha senha."
66 | notices:
67 | abandoned: "%{thing} abandonada!"
68 | adopted: "Você acaba de aprovar um %{thing}!"
69 | password_reset: "Instruções de redefinição de senha enviada! Verifique se o seu e-mail."
70 | reminder_sent: "Lembrete enviado!"
71 | signed_in: "Assinado em!"
72 | signed_out: "Assinado para fora."
73 | signed_up: "Obrigado por se inscrever!"
74 | stolen: "%{thing} roubado!"
75 | titles:
76 | adopt: "Adotar esse %{thing}"
77 | adopted: "%{thing_name} foi adotada"
78 | byline: "por %{name}"
79 | edit_profile: "Editar seu Perfil"
80 | main: "Adotar-um-%{thing}"
81 | ofline: "do %{organization}"
82 | sign_in: "Entrar para adotar essa %{thing}"
83 | thank_you: "Obrigado por adotar este %{thing}!"
84 | tos: "Termos de Serviço"
85 | sponsors:
86 | built: "Construído em Boston"
87 | cfa: "Código para a América"
88 | city: "Cidade de Boston"
89 | mayor:
90 | name: "Martin J. Walsh"
91 | title: "Mayor"
92 |
--------------------------------------------------------------------------------
/config/locales/it.yml:
--------------------------------------------------------------------------------
1 | # Please maintain alphabetical order
2 |
3 | it:
4 | buttons:
5 | abandon: "Lascia questo %{thing}"
6 | adopt: "Usalo!"
7 | back: "Indietro"
8 | change_password: "Cambia la mia password"
9 | close: "Chiudi"
10 | edit_profile: "Modifica profilo"
11 | email_password: "Mandami per Email la mia password"
12 | find: "Trova %{thing}"
13 | send_reminder: "Invia un promemoria per spalare"
14 | sign_in: "Accedi"
15 | sign_out: "Esci"
16 | sign_up: "Iscriviti"
17 | update: "Aggiorna"
18 | captions:
19 | optional: "(opzionale)"
20 | private: "(privato)"
21 | public: "(visibile ad altri)"
22 | required: "(richiesto)"
23 | defaults:
24 | address: "indirizzo"
25 | address_1: "1 City Hall Plaza"
26 | address_2: "Suite 500"
27 | city: "Boston"
28 | city_state: "Boston, Massachusetts"
29 | neighborhood: "Downtown"
30 | sms_number: "857-555-1212"
31 | state: "MA"
32 | thing: "idrante"
33 | this_thing: "Questo %{thing}"
34 | tagline: "Richiedi la responsabilità per spalare un idrante dopo una nevicata."
35 | tos: "Iscrivendoti, aderisci alle %{tos}."
36 | voice_number: "617-555-1212"
37 | zip: "02201-2013"
38 | errors:
39 | password: "E' necessario che acceda od esca prima di continuare."
40 | not_found: "Impossibile trovare %{thing}."
41 | labels:
42 | address: "Indirizzo, quartiere"
43 | address_1: "Indirizzo linea 1"
44 | address_2: "Indirizzo linea 2"
45 | city: "Città"
46 | city_state: "Città"
47 | email: "Indirizzo Email"
48 | name: "Nome"
49 | name_thing: "Dai a questo %{thing} un nome"
50 | organization: "Organizzazione"
51 | password: "Password"
52 | password_choose: "Scegli una password"
53 | password_current: "Password corrente"
54 | password_new: "Nuova password"
55 | remember_me: "Rimani connesso"
56 | sms_number: "Numero di cellulare"
57 | state: "Stato"
58 | user_existing: "Sono già registrato"
59 | user_new: "Non sono ancora registrato"
60 | voice_number: "Telefono di casa"
61 | zip: "CAP"
62 | links:
63 | feedback: "Invia un riscontro"
64 | forgot_password: "Dimenticata la password?"
65 | remembered_password: "Fa' niente. Ricordo la mia password."
66 | notices:
67 | abandoned: "%{thing} dimenticato!"
68 | adopted: "Hai appena scelto un %{thing}!"
69 | password_reset: "Inviate le istruzioni per reimpostare la password! Controlla la tua casella postale elettronica."
70 | reminder_sent: "Promemoria inviato!"
71 | signed_in: "Entrato!"
72 | signed_out: "Uscito."
73 | signed_up: "Grazie per esserti iscritto!"
74 | stolen: "%{thing} rubato!"
75 | titles:
76 | adopt: "Scegli questo %{thing}"
77 | adopted: "%{thing_name} è stato scelto"
78 | byline: "da %{name}"
79 | edit_profile: "Modifica il tuo profilo"
80 | main: "Scegli-un-%{thing}"
81 | ofline: "di %{organization}"
82 | sign_in: "Accedi per scegliere questo %{thing}"
83 | thank_you: "Grazie per aver scelto questo %{thing}!"
84 | tos: "Condizioni contrattuali"
85 | sponsors:
86 | built: "Costruito in Boston"
87 | cfa: "Code for America"
88 | city: "Città di Boston"
89 | mayor:
90 | name: "Martin J. Walsh"
91 | title: "Sindaco"
92 |
--------------------------------------------------------------------------------
/config/locales/es.yml:
--------------------------------------------------------------------------------
1 | # Please maintain alphabetical order
2 |
3 | es:
4 | buttons:
5 | abandon: "Abandona esta %{thing}"
6 | adopt: "Adoptar!"
7 | back: "De nuevo"
8 | change_password: "Cambiar mi contraseña"
9 | close: "Cerca"
10 | edit_profile: "Editar perfil"
11 | email_password: "Correo electrónico mi contraseña"
12 | find: "Encontrar %{thing}"
13 | send_reminder: "Enviar aviso a pala"
14 | sign_in: "Ingresar"
15 | sign_out: "Salir"
16 | sign_up: "Regístrate"
17 | update: "Actualización"
18 | captions:
19 | optional: "(opcional)"
20 | private: "(privado)"
21 | public: "(visible para los demás)"
22 | required: "(necesario)"
23 | defaults:
24 | address: "dirección"
25 | address_1: "1 City Hall Plaza"
26 | address_2: "Suite 500"
27 | city: "Boston"
28 | city_state: "Boston, Massachusetts"
29 | neighborhood: "Downtown"
30 | sms_number: "857-555-1212"
31 | state: "MA"
32 | thing: "boca de incendio"
33 | this_thing: "Esta %{thing}"
34 | tagline: "Reclamar la responsabilidad para palear un hidrante de incendios después de que las nieves."
35 | tos: "Al registrarse, usted está de acuerdo con los %{tos}."
36 | voice_number: "617-555-1212"
37 | zip: "02201-2013"
38 | errors:
39 | password: "Es necesario iniciar sesión o registrarse antes de continuar."
40 | not_found: "No se pudo encontrar %{thing}."
41 | labels:
42 | address: "Dirección, Barrio"
43 | address_1: "Dirección línea 1"
44 | address_2: "Dirección línea 2"
45 | city: "Ciudad"
46 | city_state: "Ciudad"
47 | email: "Dirección de correo electrónico"
48 | name: "Nombre"
49 | name_thing: "Dar a esta toma de agua un nombre"
50 | organization: "Organización"
51 | password: "Contraseña"
52 | password_choose: "Elija una contraseña"
53 | password_current: "Contraseña actual"
54 | password_new: "Una nueva contraseña"
55 | remember_me: "Mantener el"
56 | sms_number: "Número de teléfono móvil"
57 | state: "Estado"
58 | user_existing: "Ya he firmado"
59 | user_new: "No se ha inscrito"
60 | voice_number: "Número de teléfono"
61 | zip: "Código postal"
62 | links:
63 | feedback: "Envíenos sus comentarios"
64 | forgot_password: "¿Olvidaste tu contraseña?"
65 | remembered_password: "No importa. Me acordé de mi contraseña."
66 | notices:
67 | abandoned: "toma de agua abandonada!"
68 | adopted: "Usted se acaba de aprobar una %{thing}!"
69 | password_reset: "Las instrucciones de restablecimiento de contraseña enviado! Revise su correo electrónico."
70 | reminder_sent: "Recordatorio enviado!"
71 | signed_in: "Sesión!"
72 | signed_out: "La sesión."
73 | signed_up: "¡Gracias por registrarte!"
74 | stolen: "%{thing} robado!"
75 | titles:
76 | adopt: "Adoptar esta %{thing}"
77 | adopted: "%{thing_name} ha sido adoptada."
78 | byline: "por %{name}"
79 | edit_profile: "Edita tu perfil"
80 | main: "Adopt-a-%{thing}"
81 | ofline: "de %{organization}"
82 | sign_in: "Iniciar sesión para adoptar esta %{thing}"
83 | thank_you: "Gracias por la adopción de esta %{thing}!"
84 | tos: "Términos de Servicio"
85 | sponsors:
86 | built: "Construido en Boston"
87 | cfa: "Código de los Estados Unidos"
88 | city: "Ciudad de Boston"
89 | mayor:
90 | name: "Martin J. Walsh"
91 | title: "Mayor"
92 |
--------------------------------------------------------------------------------
/config/locales/fr.yml:
--------------------------------------------------------------------------------
1 | # Please maintain alphabetical order
2 |
3 | fr:
4 | buttons:
5 | abandon: "Abandonner cette %{thing}"
6 | adopt: "Adopter!"
7 | back: "Retour"
8 | change_password: "Changer mon mot de passe"
9 | close: "Fermer"
10 | edit_profile: "Modifier le profil"
11 | email_password: "Envoyez-moi mon mot de passe"
12 | find: "Trouver %{thing}"
13 | send_reminder: "Envoyer un rappel à la pelle"
14 | sign_in: "Connexion"
15 | sign_out: "Inscription sur"
16 | sign_up: "S'inscrire"
17 | update: "Mise à jour"
18 | captions:
19 | optional: "(optionnelle)"
20 | private: "(privé)"
21 | public: "(visible pour les autres)"
22 | required: "(nécessaire)"
23 | defaults:
24 | address: "Adresse"
25 | address_1: "1 City Hall Plaza"
26 | address_2: "Suite 500"
27 | city: "Boston"
28 | city_state: "Boston, Massachusetts"
29 | neighborhood: "Downtown"
30 | sms_number: "857-555-1212"
31 | state: "MA"
32 | thing: "bouche d'incendie"
33 | this_thing: "Cette %{thing}"
34 | tagline: "La responsabilité Réclamation pour pelleter une %{thing} le feu après qu'il neige."
35 | tos: "En vous inscrivant, vous acceptez les %{tos}."
36 | voice_number: "617-555-1212"
37 | zip: "02201-2013"
38 | errors:
39 | password: "Vous devez vous identifier ou vous inscrire avant de continuer."
40 | not_found: "Impossible de trouver %{thing}."
41 | labels:
42 | address: "Adresse, Quartier"
43 | address_1: "Adresse ligne 1"
44 | address_2: "Adresse ligne 2"
45 | city: "Ville"
46 | city_state: "Ville"
47 | email: "Adresse e-mail"
48 | name: "Nom"
49 | name_thing: "Donnez cette %{thing} d'un nom"
50 | organization: "Organisation"
51 | password: "Mot de passe"
52 | password_choose: "Choisissez un mot de passe"
53 | password_current: "Mot de passe actuel"
54 | password_new: "Nouveau mot de passe"
55 | remember_me: "Rester connecté"
56 | sms_number: "Numéro de téléphone portable"
57 | state: "Etat"
58 | user_existing: "J'ai déjà signé"
59 | user_new: "Je n'ai pas encore inscrits"
60 | voice_number: "Le numéro de téléphone Accueil"
61 | zip: "Code postal"
62 | links:
63 | feedback: "Envoyer des commentaires"
64 | forgot_password: "Mot de passe oublié?"
65 | remembered_password: "Jamais l'esprit. J'ai rappelé mon mot de passe."
66 | notices:
67 | abandoned: "%{thing} abandonné!"
68 | adopted: "Vous avez juste a adopté une %{thing}!"
69 | password_reset: "Les instructions de réinitialisation de mot de passe envoyé! Vérifiez votre adresse email."
70 | reminder_sent: "Rappel envoyé!"
71 | signed_in: "Signé en!"
72 | signed_out: "Signé à."
73 | signed_up: "Merci pour votre inscription!"
74 | stolen: "%{thing} volé!"
75 | titles:
76 | adopt: "Adoptez cette %{thing}"
77 | adopted: "%{thing_name} a été adoptée"
78 | byline: "par %{name}"
79 | edit_profile: "Modifiez votre profil"
80 | main: "Adopt-a-%{thing}"
81 | ofline: "de %{organization}"
82 | sign_in: "Connectez-vous à adopter cette %{thing}"
83 | thank_you: "Merci pour l'adoption de cette %{thing}!"
84 | tos: "Conditions d'utilisation"
85 | sponsors:
86 | built: "Construit à Boston"
87 | cfa: "Code pour l'Amérique"
88 | city: "Ville de Boston"
89 | mayor:
90 | name: "Martin J. Walsh"
91 | title: "Mayor"
92 |
--------------------------------------------------------------------------------
/config/locales/de.yml:
--------------------------------------------------------------------------------
1 | # Please maintain alphabetical order
2 |
3 | de:
4 | buttons:
5 | abandon: "Diesen %{thing} aufgeben"
6 | adopt: "Adoptieren"
7 | back: "Zurück"
8 | change_password: "Mein Passwort ändern"
9 | close: "Schließen"
10 | edit_profile: "Profil bearbeiten"
11 | email_password: "Bitte schicken Sie mir mein Passwort"
12 | find: "Finden Sie einen %{thing}"
13 | send_reminder: "Send Erinnerung zu schaufeln"
14 | sign_in: "Einloggen"
15 | sign_out: "Ausloggen"
16 | sign_up: "Registrieren"
17 | update: "Aktualisierung"
18 | captions:
19 | optional: "(optional)"
20 | private: "(privat)"
21 | public: "(für Andere sichtbar)"
22 | required: "(erforderlich)"
23 | defaults:
24 | address: "Adresse"
25 | address_1: "1 City Hall Plaza"
26 | address_2: "Suite 500"
27 | city: "Boston"
28 | city_state: "Boston, Massachusetts"
29 | neighborhood: "Downtown"
30 | sms_number: "857-555-1212"
31 | state: "MA"
32 | thing: "Hydrant"
33 | this_thing: "Dieser %{thing}"
34 | tagline: "Verantwortung dafür übernehmen, einen Hydranten auszubuddeln, nachdem es geschneit hat."
35 | tos: "Mit der Anmeldung erklären Sie sich mit den %{tos} einverstanden."
36 | voice_number: "617-555-1212"
37 | zip: "02201-2013"
38 | errors:
39 | password: "Sie müssen sich einloggen oder registrieren, um fortzufahren."
40 | not_found: "%{thing} konnte nicht gefunden werden."
41 | labels:
42 | address: "Adresse, Stadtviertel"
43 | address_1: "Adresszeile 1"
44 | address_2: "Adresszeile 2"
45 | city: "Stadt"
46 | city_state: "Stadt"
47 | email: "E-Mail-Adresse"
48 | name: "Name"
49 | name_thing: "Geben Sie diesem %{thing} einen Namen"
50 | organization: "Organisation"
51 | password: "Passwort"
52 | password_choose: "Wählen Sie ein Passwort"
53 | password_current: "Aktuelles Passwort"
54 | password_new: "Neues Passwort"
55 | remember_me: "Eingeloggt bleiben"
56 | sms_number: "Handy-Nummer"
57 | state: "Zustand"
58 | user_existing: "Ich habe mich bereits registriert"
59 | user_new: "Ich habe mich noch nicht registriert"
60 | voice_number: "Startseite Telefonnummer"
61 | zip: "Postleitzahl"
62 | links:
63 | feedback: "Feedback senden"
64 | forgot_password: "Passwort vergessen?"
65 | remembered_password: "Das macht nichts. Ich erinnerte mich an mein Passwort vergessen."
66 | notices:
67 | abandoned: "%{thing} verlassen!"
68 | adopted: "Sie haben soeben einen %{thing} adoptiert!"
69 | password_reset: "Password Reset Anleitung geschickt! Überprüfen Sie Ihre E-Mail."
70 | reminder_sent: "Reminder versendet!"
71 | signed_in: "Unterzeichnet in!"
72 | signed_out: "Abgemeldet."
73 | signed_up: "Vielen Dank für Ihre Anmeldung!"
74 | stolen: "%{thing} gestohlen!"
75 | titles:
76 | adopt: "Adoptieren diesen %{thing}"
77 | adopted: "{thing_name} wurde adoptiert"
78 | byline: "von %{name}"
79 | edit_profile: "Ihr Profil bearbeiten"
80 | main: "%{thing} adoptieren"
81 | ofline: "von %{organization}"
82 | sign_in: "Melden Sie sich an um diesen %{thing} zu übernehmen"
83 | thank_you: "Vielen Dank für die Annahme dieser %{thing}!"
84 | tos: "Nutzungsbedingungen"
85 | sponsors:
86 | built: "Erbaut in Boston"
87 | cfa: "Code for Amerika"
88 | city: "City of Boston"
89 | mayor:
90 | name: "Martin J. Walsh"
91 | title: "Mayor"
92 |
--------------------------------------------------------------------------------
/app/views/sidebar/_combo_form.html.haml:
--------------------------------------------------------------------------------
1 | = form_for :user, :html => {:id => "combo-form", :class => "form-vertical"} do |f|
2 | %fieldset#common_fields
3 | .control-group
4 | %label{:for => "user_email", :id => "user_email_label"}
5 | = t("labels.email")
6 | %small
7 | = image_tag "lock.png", :class => "lock", :alt => t("captions.private"), :title => t("captions.private")
8 | = f.email_field "email", :value => params[:user] ? params[:user][:email] : nil
9 | .control-group.radio
10 | = f.label "new" , radio_button_tag("user", "new", true).html_safe + t("labels.user_new")
11 | = f.label "existing", radio_button_tag("user", "existing").html_safe + t("labels.user_existing")
12 | %fieldset#user_sign_up_fields
13 | .control-group
14 | %label{:for => "user_name", :id => "user_name_label"}
15 | = t("labels.name")
16 | %small
17 | = t("captions.public")
18 | = f.text_field "name"
19 | .control-group
20 | %label{:for => "user_organization", :id => "user_organization_label"}
21 | = t("labels.organization")
22 | %small
23 | = t("captions.public")
24 | = f.text_field "organization"
25 | .control-group
26 | %label{:for => "user_voice_number", :id => "user_voice_number_label"}
27 | = t("labels.voice_number")
28 | %small
29 | = image_tag "lock.png", :class => "lock", :alt => t("captions.private"), :title => t("captions.private")
30 | = f.telephone_field "voice_number", :placeholder => t("defaults.voice_number")
31 | .control-group
32 | %label{:for => "user_sms_number", :id => "user_sms_number_label"}
33 | = t("labels.sms_number")
34 | %small
35 | = image_tag "lock.png", :class => "lock", :alt => t("captions.private"), :title => t("captions.private")
36 | = f.telephone_field "sms_number", :placeholder => t("defaults.sms_number")
37 | .control-group
38 | %label{:for => "user_password_confirmation", :id => "user_password_confirmation_label"}
39 | = t("labels.password_choose")
40 | %small
41 | = image_tag "lock.png", :class => "lock", :alt => t("captions.private"), :title => t("captions.private")
42 | = f.password_field "password_confirmation"
43 | .form-actions
44 | = f.submit t("buttons.sign_up"), :class => "btn btn-primary"
45 | %p
46 | = t("defaults.tos", :tos => link_to(t("titles.tos"), "#tos", :id => "tos_link", :"data-toggle" => "modal")).html_safe
47 | %fieldset#user_sign_in_fields{:style => "display: none;"}
48 | .control-group
49 | %label{:for => "user_password", :id => "user_password_label"}
50 | = t("labels.password")
51 | %small
52 | = image_tag "lock.png", :class => "lock", :alt => t("captions.private"), :title => t("captions.private")
53 | = f.password_field "password"
54 | .control-group
55 | = f.label "remember_me" , f.check_box("remember_me", :checked => true).html_safe + t("labels.remember_me")
56 | .form-actions
57 | = f.submit t("buttons.sign_in"), :class => "btn btn-primary"
58 | %p
59 | = link_to t("links.forgot_password"), "#", :id => "user_forgot_password_link"
60 | %fieldset#user_forgot_password_fields{:style => "display: none;"}
61 | .form-actions
62 | = f.submit t("buttons.email_password"), :class => "btn btn-primary"
63 | %p
64 | = link_to t("links.remembered_password"), "#", :id => "user_remembered_password_link"
65 | = render :partial => "sidebar/tos"
66 | :javascript
67 | $(function() {
68 | $('#user_email').focus();
69 | });
70 |
--------------------------------------------------------------------------------
/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: 5) do
15 |
16 | # These are extensions that must be enabled in order to support this database
17 | enable_extension "plpgsql"
18 |
19 | create_table "rails_admin_histories", force: :cascade do |t|
20 | t.string "message"
21 | t.string "username"
22 | t.integer "item"
23 | t.string "table"
24 | t.integer "month", limit: 2
25 | t.integer "year", limit: 8
26 | t.datetime "created_at"
27 | t.datetime "updated_at"
28 | end
29 |
30 | add_index "rails_admin_histories", ["item", "table", "month", "year"], name: "index_rails_admin_histories", using: :btree
31 |
32 | create_table "reminders", force: :cascade do |t|
33 | t.datetime "created_at"
34 | t.datetime "updated_at"
35 | t.integer "from_user_id", null: false
36 | t.integer "to_user_id", null: false
37 | t.integer "thing_id", null: false
38 | t.boolean "sent", default: false
39 | end
40 |
41 | add_index "reminders", ["from_user_id"], name: "index_reminders_on_from_user_id", using: :btree
42 | add_index "reminders", ["sent"], name: "index_reminders_on_sent", using: :btree
43 | add_index "reminders", ["thing_id"], name: "index_reminders_on_thing_id", using: :btree
44 | add_index "reminders", ["to_user_id"], name: "index_reminders_on_to_user_id", using: :btree
45 |
46 | create_table "things", force: :cascade do |t|
47 | t.datetime "created_at"
48 | t.datetime "updated_at"
49 | t.string "name"
50 | t.decimal "lat", precision: 16, scale: 14, null: false
51 | t.decimal "lng", precision: 17, scale: 14, null: false
52 | t.integer "city_id"
53 | t.integer "user_id"
54 | end
55 |
56 | add_index "things", ["city_id"], name: "index_things_on_city_id", unique: true, using: :btree
57 |
58 | create_table "users", force: :cascade do |t|
59 | t.datetime "created_at"
60 | t.datetime "updated_at"
61 | t.string "name", null: false
62 | t.string "organization"
63 | t.string "email", null: false
64 | t.string "voice_number"
65 | t.string "sms_number"
66 | t.string "address_1"
67 | t.string "address_2"
68 | t.string "city"
69 | t.string "state"
70 | t.string "zip"
71 | t.boolean "admin", default: false
72 | t.string "encrypted_password", default: "", null: false
73 | t.string "reset_password_token"
74 | t.datetime "reset_password_sent_at"
75 | t.datetime "remember_created_at"
76 | t.integer "sign_in_count", default: 0, null: false
77 | t.datetime "current_sign_in_at"
78 | t.datetime "last_sign_in_at"
79 | t.string "current_sign_in_ip"
80 | t.string "last_sign_in_ip"
81 | end
82 |
83 | add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
84 | add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
85 |
86 | end
87 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.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 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application
18 | # Add `rack-cache` to your Gemfile before enabling this.
19 | # For large-scale production use, consider using a caching reverse proxy like
20 | # NGINX, varnish or squid.
21 | # config.action_dispatch.rack_cache = true
22 |
23 | # Disable serving static files from the `/public` folder by default since
24 | # Apache or NGINX already handles this.
25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
26 |
27 | # Compress JavaScripts and CSS.
28 | config.assets.js_compressor = :uglifier
29 | # config.assets.css_compressor = :sass
30 |
31 | # Do not fallback to assets pipeline if a precompiled asset is missed.
32 | config.assets.compile = false
33 |
34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets,
35 | # yet still be able to expire them through the digest params.
36 | config.assets.digest = true
37 |
38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
39 |
40 | # Specifies the header that your server uses for sending files.
41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
43 |
44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
45 | # config.force_ssl = true
46 |
47 | # Use the lowest log level to ensure availability of diagnostic information
48 | # when problems arise.
49 | config.log_level = :debug
50 |
51 | # Prepend all log lines with the following tags.
52 | # config.log_tags = [ :subdomain, :uuid ]
53 |
54 | # Use a different logger for distributed setups.
55 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
56 |
57 | # Use a different cache store in production.
58 | # config.cache_store = :mem_cache_store
59 |
60 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
61 | # config.action_controller.asset_host = 'http://assets.example.com'
62 |
63 | # Ignore bad email addresses and do not raise email delivery errors.
64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
65 | config.action_mailer.raise_delivery_errors = true
66 | config.action_mailer.delivery_method = :smtp
67 | config.action_mailer.default_url_options = {host: 'adoptahydrant.org'}
68 |
69 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
70 | # the I18n.default_locale when a translation cannot be found).
71 | config.i18n.fallbacks = true
72 |
73 | # Send deprecation notices to registered listeners.
74 | config.active_support.deprecation = :notify
75 |
76 | # Use default logging formatter so that PID and timestamp are not suppressed.
77 | config.log_formatter = ::Logger::Formatter.new
78 |
79 | # Do not dump schema after migrations.
80 | config.active_record.dump_schema_after_migration = false
81 | end
82 |
83 | ActionMailer::Base.smtp_settings = {
84 | address: 'smtp.sendgrid.net',
85 | port: '25',
86 | authentication: :plain,
87 | user_name: ENV['SENDGRID_USERNAME'],
88 | password: ENV['SENDGRID_PASSWORD'],
89 | domain: ENV['SENDGRID_DOMAIN'],
90 | }
91 |
--------------------------------------------------------------------------------
/config/locales/devise.en.yml:
--------------------------------------------------------------------------------
1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n
2 |
3 | en:
4 | devise:
5 | confirmations:
6 | confirmed: "Your account was successfully confirmed."
7 | send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes."
8 | 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."
9 | failure:
10 | already_authenticated: "You are already signed in."
11 | inactive: "Your account is not activated yet."
12 | invalid: "Invalid email or password."
13 | locked: "Your account is locked."
14 | last_attempt: "You have one more attempt before your account will be locked."
15 | not_found_in_database: "Invalid email or password."
16 | timeout: "Your session expired. Please sign in again to continue."
17 | unauthenticated: "You need to sign in or sign up before continuing."
18 | unconfirmed: "You have to confirm your account before continuing."
19 | mailer:
20 | confirmation_instructions:
21 | subject: "Confirmation instructions"
22 | reset_password_instructions:
23 | subject: "Reset password instructions"
24 | unlock_instructions:
25 | subject: "Unlock Instructions"
26 | omniauth_callbacks:
27 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
28 | success: "Successfully authenticated from %{kind} account."
29 | passwords:
30 | 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."
31 | send_instructions: "You will receive an email with instructions about how to reset your password in a few minutes."
32 | 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."
33 | updated: "Your password was changed successfully. You are now signed in."
34 | updated_not_active: "Your password was changed successfully."
35 | registrations:
36 | destroyed: "Bye! Your account was successfully cancelled. We hope to see you again soon."
37 | signed_up: "Welcome! You have signed up successfully."
38 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
39 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
40 | 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."
41 | 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."
42 | updated: "You updated your account successfully."
43 | sessions:
44 | signed_in: "Signed in successfully."
45 | signed_out: "Signed out successfully."
46 | unlocks:
47 | send_instructions: "You will receive an email with instructions about how to unlock your account in a few minutes."
48 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions about how to unlock it in a few minutes."
49 | unlocked: "Your account has been unlocked successfully. Please sign in to continue."
50 | errors:
51 | messages:
52 | already_confirmed: "was already confirmed, please try signing in"
53 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
54 | expired: "has expired, please request a new one"
55 | not_found: "not found"
56 | not_locked: "was not locked"
57 | not_saved:
58 | one: "1 error prohibited this %{resource} from being saved:"
59 | other: "%{count} errors prohibited this %{resource} from being saved:"
60 |
--------------------------------------------------------------------------------
/app/views/sidebar/edit_profile.html.haml:
--------------------------------------------------------------------------------
1 | = form_for resource, :as => resource_name, :url => registration_path(resource_name), :html => {:id => "edit_form", :method => :put} do |f|
2 | = f.hidden_field "id"
3 | %fieldset.control-group
4 | %label{:for => "user_email", :id => "user_email_label"}
5 | = t("labels.email")
6 | %small
7 | = image_tag "lock.png", :class => "lock", :alt => t("captions.private"), :title => t("captions.private")
8 | = f.email_field "email"
9 | %fieldset.control-group
10 | %label{:for => "user_name", :id => "user_name_label"}
11 | = t("labels.name")
12 | %small
13 | = t("captions.public")
14 | = f.text_field "name"
15 | %fieldset.control-group
16 | %label{:for => "user_organization", :id => "user_organization_label"}
17 | = t("labels.organization")
18 | %small
19 | = t("captions.public")
20 | = f.text_field "organization"
21 | %fieldset.control-group
22 | %label{:for => "user_voice_number", :id => "user_voice_number_label"}
23 | = t("labels.voice_number")
24 | %small
25 | = image_tag "lock.png", :class => "lock", :alt => t("captions.private"), :title => t("captions.private")
26 | = f.telephone_field "voice_number", :placeholder => t("defaults.voice_number"), :value => number_to_phone(f.object.voice_number)
27 | %fieldset.control-group
28 | %label{:for => "user_sms_number", :id => "user_sms_number_label"}
29 | = t("labels.sms_number")
30 | %small
31 | = image_tag "lock.png", :class => "lock", :alt => t("captions.private"), :title => t("captions.private")
32 | = f.telephone_field "sms_number", :placeholder => t("defaults.sms_number"), :value => number_to_phone(f.object.sms_number)
33 | %fieldset.control-group
34 | %label{:for => "user_address_1", :id => "user_address_1_label"}
35 | = t("labels.address_1")
36 | %small
37 | = image_tag "lock.png", :class => "lock", :alt => t("captions.private"), :title => t("captions.private")
38 | = f.text_field "address_1", :placeholder => t("defaults.address_1")
39 | %fieldset.control-group
40 | %label{:for => "user_address_2", :id => "user_address_2_label"}
41 | = t("labels.address_2")
42 | %small
43 | = image_tag "lock.png", :class => "lock", :alt => t("captions.private"), :title => t("captions.private")
44 | = f.text_field "address_2", :placeholder => t("defaults.address_2")
45 | %fieldset.control-group
46 | %label{:for => "user_city", :id => "user_city_label"}
47 | = t("labels.city")
48 | %small
49 | = image_tag "lock.png", :class => "lock", :alt => t("captions.private"), :title => t("captions.private")
50 | = f.text_field "city", :placeholder => t("defaults.city")
51 | %fieldset.control-group
52 | %label{:for => "user_state", :id => "user_state_label"}
53 | = t("labels.state")
54 | %small
55 | = image_tag "lock.png", :class => "lock", :alt => t("captions.private"), :title => t("captions.private")
56 | = f.select "state", us_states, :include_blank => true
57 | %fieldset.control-group
58 | %label{:for => "user_zip", :id => "user_zip_label"}
59 | = t("labels.zip")
60 | %small
61 | = image_tag "lock.png", :class => "lock", :alt => t("captions.private"), :title => t("captions.private")
62 | = f.text_field "zip", :placeholder => t("defaults.zip")
63 | %fieldset.control-group
64 | %label{:for => "user_password", :id => "user_password_label"}
65 | = t("labels.password_new")
66 | %small
67 | = t("captions.optional")
68 | = f.password_field "password"
69 | %fieldset.control-group
70 | %label{:for => "user_current_password", :id => "user_current_password_label"}
71 | = t("labels.password_current")
72 | %small
73 | = t("captions.required")
74 | = f.password_field "current_password"
75 | %fieldset.form-actions
76 | = f.submit t("buttons.update"), :class => "btn btn-primary"
77 | %a{:href => root_path, :id => "back_link", :class => "btn"}
78 | = t("buttons.back")
79 | :javascript
80 | $(function() {
81 | $('#user_email').focus();
82 | });
83 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/screen.css:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | }
4 |
5 | body {
6 | height: 100%;
7 | margin: 0;
8 | }
9 |
10 | h1, h2 {
11 | text-align: center;
12 | margin: 20px 0 10px;
13 | }
14 |
15 | h2 {
16 | font-size: 22px;
17 | line-height: 30px;
18 | margin: 10px 0;
19 | }
20 |
21 | form {
22 | margin-bottom: 0;
23 | }
24 |
25 | label {
26 | text-align: left;
27 | }
28 |
29 | input {
30 | width: 200px;
31 | }
32 |
33 | .sidebar input, .sidebar select, .sidebar option, .sidebar button, .sidebar form.search-form input, .sidebar form.search-form select, .sidebar form.search-form option, .sidebar a.btn {
34 | display: block;
35 | margin-bottom: 10px;
36 | }
37 |
38 | input[type="radio"], input[type="checkbox"] {
39 | display: inline;
40 | width: auto;
41 | margin-right: 10px;
42 | }
43 |
44 | input#user_new {
45 | margin-bottom: 0;
46 | }
47 |
48 | input#user_existing {
49 | margin-bottom: 10px;
50 | }
51 |
52 | form select, button.btn, input[type="submit"].btn {
53 | width: 210px;
54 | }
55 |
56 | form input.search-query {
57 | -moz-border-radius: 14px;
58 | -webkit-border-radius: 14px;
59 | border-radius: 14px;
60 | margin-bottom: 0;
61 | padding-left: 14px;
62 | padding-right: 14px;
63 | width: 180px;
64 | }
65 |
66 | .sidebar p {
67 | padding: 0 20px;
68 | margin: 0;
69 | }
70 |
71 | a.btn {
72 | width: 188px;
73 | }
74 |
75 | .table {
76 | display: table;
77 | height: 100%;
78 | width: 100%;
79 | }
80 |
81 | .table-row {
82 | display: table-row;
83 | }
84 |
85 | .table-cell {
86 | display: table-cell;
87 | height: 100%;
88 | vertical-align: top;
89 | }
90 |
91 | .alert-message.block-message {
92 | padding: 8px 12px 8px;
93 | margin-bottom: 10px;
94 | }
95 |
96 | .sidebar .alert-message.block-message {
97 | -webkit-border-radius: 0;
98 | -moz-border-radius: 0;
99 | border-radius: 0;
100 | text-align: left;
101 | padding-left: 10px;
102 | }
103 |
104 | .sidebar {
105 | width: 250px;
106 | border-right: 1px solid #ccc;
107 | text-align: center;
108 | }
109 |
110 | .sidebar .control-group {
111 | margin-left: 20px;
112 | }
113 |
114 | .sidebar .form-actions {
115 | padding: 17px 20px 10px;
116 | }
117 |
118 | .sidebar p#tagline {
119 | color: #ffffff;
120 | background-color: #c43c35;
121 | background-repeat: repeat-x;
122 | background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));
123 | background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
124 | background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
125 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));
126 | background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
127 | background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
128 | background-image: linear-gradient(top, #ee5f5b, #c43c35);
129 | border-color: #c43c35 #c43c35 #882a25;
130 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
131 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);
132 | padding: 10px 20px;
133 | text-align: center;
134 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
135 | }
136 |
137 | .sidebar #logos {
138 | height: 100px;
139 | }
140 |
141 | .sidebar #logos #col1 {
142 | float: left;
143 | margin-left: 20px;
144 | width: 100px;
145 | }
146 |
147 | .sidebar #logos #col1 img {
148 | float: left;
149 | margin: 5px 0;
150 | }
151 |
152 | .sidebar #logos #col2 {
153 | float: left;
154 | margin-left: 10px;
155 | width: 100px;
156 | }
157 |
158 | .sidebar #mayor {
159 | font-family: "Times New Roman", Times, serif;
160 | font-size: 8px;
161 | padding: 0;
162 | margin: -5px 0 0 0;
163 | }
164 |
165 | .sidebar #mayor a, .sidebar #mayor a:active, .sidebar #mayor a:hover, .sidebar #mayor a:visited {
166 | color: #000;
167 | text-decoration: none;
168 | }
169 |
170 | .sidebar #feedback {
171 | margin-top: 5px;
172 | }
173 |
174 | .map-container {
175 | width: auto;
176 | }
177 |
178 | #map {
179 | height: 100%;
180 | width: 100%;
181 | }
182 |
183 | #tos {
184 | text-align: left;
185 | }
186 |
187 | #tos p {
188 | padding: 0;
189 | margin-bottom: 10px;
190 | }
191 |
192 | .upcase {
193 | text-transform: uppercase;
194 | }
195 |
196 | .alpha {
197 | list-style-type: lower-alpha;
198 | }
199 |
200 | .roman {
201 | list-style-type: lower-roman;
202 | }
203 |
204 | img.lock {
205 | height: 9px;
206 | width: 7px;
207 | opacity: 0.8;
208 | filter: alpha(opacity=80); /* For IE8 and earlier */
209 | }
210 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Adopt-a-Hydrant
2 |
3 | [][travis]
4 | [][gemnasium]
5 | [][coveralls]
6 |
7 | [travis]: http://travis-ci.org/codeforamerica/adopt-a-hydrant
8 | [gemnasium]: https://gemnasium.com/codeforamerica/adopt-a-hydrant
9 | [coveralls]: https://coveralls.io/r/codeforamerica/adopt-a-hydrant
10 |
11 | Claim responsibility for shoveling out a fire hydrant after it snows.
12 |
13 | ## Screenshot
14 | 
15 |
16 | ## Demo
17 | You can see a running version of the application at
18 | [http://adopt-a-hydrant.herokuapp.com/][demo].
19 |
20 | [demo]: http://adopt-a-hydrant.herokuapp.com/
21 |
22 | ## Installation
23 | This application requires [Postgres](http://www.postgresql.org/) to be installed
24 |
25 | git clone git://github.com/codeforamerica/adopt-a-hydrant.git
26 | cd adopt-a-hydrant
27 | bundle install
28 |
29 | bundle exec rake db:create
30 | bundle exec rake db:schema:load
31 |
32 | ## Usage
33 | rails server
34 |
35 | ## Seed Data
36 | bundle exec rake db:seed
37 |
38 | ## Deploying to Heroku
39 | A successful deployment to Heroku requires a few setup steps:
40 |
41 | 1. Generate a new secret token:
42 |
43 | ```
44 | rake secret
45 | ```
46 |
47 | 2. Set the token on Heroku:
48 |
49 | ```
50 | heroku config:set SECRET_TOKEN=the_token_you_generated
51 | ```
52 |
53 | 3. [Precompile your assets](https://devcenter.heroku.com/articles/rails3x-asset-pipeline-cedar)
54 |
55 | ```
56 | RAILS_ENV=production bundle exec rake assets:precompile
57 |
58 | git add public/assets
59 |
60 | git commit -m "vendor compiled assets"
61 | ```
62 |
63 | 4. Add a production database to config/database.yml
64 |
65 | 5. Seed the production db:
66 |
67 | `heroku run bundle exec rake db:seed`
68 |
69 | Keep in mind that the Heroku free Postgres plan only allows up to 10,000 rows,
70 | so if your city has more than 10,000 fire hydrants (or other thing to be
71 | adopted), you will need to upgrade to the $9/month plan.
72 |
73 | ### Google Analytics
74 | If you have a Google Analytics account you want to use to track visits to your
75 | deployment of this app, just set your ID and your domain name as environment
76 | variables:
77 |
78 | heroku config:set GOOGLE_ANALYTICS_ID=your_id
79 | heroku config:set GOOGLE_ANALYTICS_DOMAIN=your_domain_name
80 |
81 | An example ID is `UA-12345678-9`, and an example domain is `adoptahydrant.org`.
82 |
83 | ## Contributing
84 | In the spirit of [free software][free-sw], **everyone** is encouraged to help
85 | improve this project.
86 |
87 | [free-sw]: http://www.fsf.org/licensing/essays/free-sw.html
88 |
89 | Here are some ways *you* can contribute:
90 |
91 | * by using alpha, beta, and prerelease versions
92 | * by reporting bugs
93 | * by suggesting new features
94 | * by [translating to a new language][locales]
95 | * by writing or editing documentation
96 | * by writing specifications
97 | * by writing code (**no patch is too small**: fix typos, add comments, clean up
98 | inconsistent whitespace)
99 | * by refactoring code
100 | * by closing [issues][]
101 | * by reviewing patches
102 | * [financially][]
103 |
104 | [locales]: https://github.com/codeforamerica/adopt-a-hydrant/tree/master/config/locales
105 | [issues]: https://github.com/codeforamerica/adopt-a-hydrant/issues
106 | [financially]: https://secure.codeforamerica.org/page/contribute
107 |
108 | ## Submitting an Issue
109 | We use the [GitHub issue tracker][issues] to track bugs and features. Before
110 | submitting a bug report or feature request, check to make sure it hasn't
111 | already been submitted. When submitting a bug report, please include a [Gist][]
112 | that includes a stack trace and any details that may be necessary to reproduce
113 | the bug, including your gem version, Ruby version, and operating system.
114 | Ideally, a bug report should include a pull request with failing specs.
115 |
116 | [gist]: https://gist.github.com/
117 |
118 | ## Submitting a Pull Request
119 | 1. [Fork the repository.][fork]
120 | 2. [Create a topic branch.][branch]
121 | 3. Add specs for your unimplemented feature or bug fix.
122 | 4. Run `bundle exec rake test`. If your specs pass, return to step 3.
123 | 5. Implement your feature or bug fix.
124 | 6. Run `bundle exec rake test`. If your specs fail, return to step 5.
125 | 7. Run `open coverage/index.html`. If your changes are not completely covered
126 | by your tests, return to step 3.
127 | 8. Add, commit, and push your changes.
128 | 9. [Submit a pull request.][pr]
129 |
130 | [fork]: http://help.github.com/fork-a-repo/
131 | [branch]: http://learn.github.com/p/branching.html
132 | [pr]: http://help.github.com/send-pull-requests/
133 |
134 | ## Supported Ruby Version
135 | This library aims to support and is [tested against][travis] Ruby version 2.3.0.
136 |
137 | If something doesn't work on this version, it should be considered a bug.
138 |
139 | This library may inadvertently work (or seem to work) on other Ruby
140 | implementations, however support will only be provided for the version above.
141 |
142 | If you would like this library to support another Ruby version, you may
143 | volunteer to be a maintainer. Being a maintainer entails making sure all tests
144 | run and pass on that implementation. When something breaks on your
145 | implementation, you will be personally responsible for providing patches in a
146 | timely fashion. If critical issues for a particular implementation exist at the
147 | time of a major release, support for that Ruby version may be dropped.
148 |
149 | ## Copyright
150 | Copyright (c) 2014 Code for America. See [LICENSE][] for details.
151 |
152 | [license]: https://github.com/codeforamerica/adopt-a-hydrant/blob/master/LICENSE.md
153 |
154 | [][tracker]
155 |
156 | [tracker]: http://stats.codeforamerica.org/projects/adopt-a-hydrant
157 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | actionmailer (4.2.7.1)
5 | actionpack (= 4.2.7.1)
6 | actionview (= 4.2.7.1)
7 | activejob (= 4.2.7.1)
8 | mail (~> 2.5, >= 2.5.4)
9 | rails-dom-testing (~> 1.0, >= 1.0.5)
10 | actionpack (4.2.7.1)
11 | actionview (= 4.2.7.1)
12 | activesupport (= 4.2.7.1)
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.7.1)
18 | activesupport (= 4.2.7.1)
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.2)
23 | activejob (4.2.7.1)
24 | activesupport (= 4.2.7.1)
25 | globalid (>= 0.3.0)
26 | activemodel (4.2.7.1)
27 | activesupport (= 4.2.7.1)
28 | builder (~> 3.1)
29 | activerecord (4.2.7.1)
30 | activemodel (= 4.2.7.1)
31 | activesupport (= 4.2.7.1)
32 | arel (~> 6.0)
33 | activesupport (4.2.7.1)
34 | i18n (~> 0.7)
35 | json (~> 1.7, >= 1.7.7)
36 | minitest (~> 5.1)
37 | thread_safe (~> 0.3, >= 0.3.4)
38 | tzinfo (~> 1.1)
39 | addressable (2.4.0)
40 | arel (6.0.3)
41 | ast (2.3.0)
42 | bcrypt (3.1.11)
43 | builder (3.2.2)
44 | coffee-rails (4.2.1)
45 | coffee-script (>= 2.2.0)
46 | railties (>= 4.0.0, < 5.2.x)
47 | coffee-script (2.4.1)
48 | coffee-script-source
49 | execjs
50 | coffee-script-source (1.10.0)
51 | concurrent-ruby (1.0.2)
52 | coveralls (0.8.15)
53 | json (>= 1.8, < 3)
54 | simplecov (~> 0.12.0)
55 | term-ansicolor (~> 1.3)
56 | thor (~> 0.19.1)
57 | tins (>= 1.6.0, < 2)
58 | crack (0.4.3)
59 | safe_yaml (~> 1.0.0)
60 | devise (4.2.0)
61 | bcrypt (~> 3.0)
62 | orm_adapter (~> 0.1)
63 | railties (>= 4.1.0, < 5.1)
64 | responders
65 | warden (~> 1.2.3)
66 | docile (1.1.5)
67 | erubis (2.7.0)
68 | execjs (2.7.0)
69 | fastercsv (1.5.5)
70 | font-awesome-rails (4.6.3.1)
71 | railties (>= 3.2, < 5.1)
72 | geokit (1.10.0)
73 | globalid (0.3.7)
74 | activesupport (>= 4.1.0)
75 | haml (4.0.7)
76 | tilt
77 | hashdiff (0.3.0)
78 | http_accept_language (2.0.5)
79 | i18n (0.7.0)
80 | jquery-rails (4.2.1)
81 | rails-dom-testing (>= 1, < 3)
82 | railties (>= 4.2.0)
83 | thor (>= 0.14, < 2.0)
84 | jquery-ui-rails (5.0.5)
85 | railties (>= 3.2.16)
86 | json (1.8.3)
87 | kaminari (0.17.0)
88 | actionpack (>= 3.0.0)
89 | activesupport (>= 3.0.0)
90 | loofah (2.0.3)
91 | nokogiri (>= 1.5.9)
92 | mail (2.6.4)
93 | mime-types (>= 1.16, < 4)
94 | mime-types (3.1)
95 | mime-types-data (~> 3.2015)
96 | mime-types-data (3.2016.0521)
97 | mini_portile2 (2.1.0)
98 | minitest (5.9.1)
99 | nested_form (0.3.2)
100 | nokogiri (1.6.8.1)
101 | mini_portile2 (~> 2.1.0)
102 | orm_adapter (0.5.0)
103 | parser (2.3.1.4)
104 | ast (~> 2.2)
105 | pg (0.19.0)
106 | powerpack (0.1.1)
107 | puma (3.6.0)
108 | rack (1.6.4)
109 | rack-pjax (1.0.0)
110 | nokogiri (~> 1.5)
111 | rack (>= 1.1)
112 | rack-test (0.6.3)
113 | rack (>= 1.0)
114 | rails (4.2.7.1)
115 | actionmailer (= 4.2.7.1)
116 | actionpack (= 4.2.7.1)
117 | actionview (= 4.2.7.1)
118 | activejob (= 4.2.7.1)
119 | activemodel (= 4.2.7.1)
120 | activerecord (= 4.2.7.1)
121 | activesupport (= 4.2.7.1)
122 | bundler (>= 1.3.0, < 2.0)
123 | railties (= 4.2.7.1)
124 | sprockets-rails
125 | rails-deprecated_sanitizer (1.0.3)
126 | activesupport (>= 4.2.0.alpha)
127 | rails-dom-testing (1.0.7)
128 | activesupport (>= 4.2.0.beta, < 5.0)
129 | nokogiri (~> 1.6.0)
130 | rails-deprecated_sanitizer (>= 1.0.1)
131 | rails-html-sanitizer (1.0.3)
132 | loofah (~> 2.0)
133 | rails_12factor (0.0.3)
134 | rails_serve_static_assets
135 | rails_stdout_logging
136 | rails_admin (1.0.0)
137 | builder (~> 3.1)
138 | coffee-rails (~> 4.0)
139 | font-awesome-rails (>= 3.0, < 5)
140 | haml (~> 4.0)
141 | jquery-rails (>= 3.0, < 5)
142 | jquery-ui-rails (~> 5.0)
143 | kaminari (~> 0.14)
144 | nested_form (~> 0.3)
145 | rack-pjax (>= 0.7)
146 | rails (>= 4.0, < 6)
147 | remotipart (~> 1.3)
148 | sass-rails (>= 4.0, < 6)
149 | rails_serve_static_assets (0.0.5)
150 | rails_stdout_logging (0.0.5)
151 | railties (4.2.7.1)
152 | actionpack (= 4.2.7.1)
153 | activesupport (= 4.2.7.1)
154 | rake (>= 0.8.7)
155 | thor (>= 0.18.1, < 2.0)
156 | rainbow (2.1.0)
157 | rake (11.3.0)
158 | remotipart (1.3.1)
159 | responders (2.3.0)
160 | railties (>= 4.2.0, < 5.1)
161 | rubocop (0.43.0)
162 | parser (>= 2.3.1.1, < 3.0)
163 | powerpack (~> 0.1)
164 | rainbow (>= 1.99.1, < 3.0)
165 | ruby-progressbar (~> 1.7)
166 | unicode-display_width (~> 1.0, >= 1.0.1)
167 | ruby-progressbar (1.8.1)
168 | safe_yaml (1.0.4)
169 | sass (3.4.22)
170 | sass-rails (5.0.6)
171 | railties (>= 4.0.0, < 6)
172 | sass (~> 3.1)
173 | sprockets (>= 2.8, < 4.0)
174 | sprockets-rails (>= 2.0, < 4.0)
175 | tilt (>= 1.1, < 3)
176 | simplecov (0.12.0)
177 | docile (~> 1.1.0)
178 | json (>= 1.8, < 3)
179 | simplecov-html (~> 0.10.0)
180 | simplecov-html (0.10.0)
181 | skylight (0.10.6)
182 | activesupport (>= 3.0.0)
183 | spring (2.0.0)
184 | activesupport (>= 4.2)
185 | sprockets (3.7.0)
186 | concurrent-ruby (~> 1.0)
187 | rack (> 1, < 3)
188 | sprockets-rails (3.2.0)
189 | actionpack (>= 4.0)
190 | activesupport (>= 4.0)
191 | sprockets (>= 3.0.0)
192 | sqlite3 (1.3.12)
193 | term-ansicolor (1.4.0)
194 | tins (~> 1.0)
195 | thor (0.19.1)
196 | thread_safe (0.3.5)
197 | tilt (2.0.5)
198 | tins (1.12.0)
199 | tzinfo (1.2.2)
200 | thread_safe (~> 0.1)
201 | uglifier (3.0.2)
202 | execjs (>= 0.3.0, < 3)
203 | unicode-display_width (1.1.1)
204 | validates_formatting_of (0.9.0)
205 | activemodel
206 | warden (1.2.6)
207 | rack (>= 1.0)
208 | webmock (2.1.0)
209 | addressable (>= 2.3.6)
210 | crack (>= 0.3.2)
211 | hashdiff
212 |
213 | PLATFORMS
214 | ruby
215 |
216 | DEPENDENCIES
217 | arel
218 | coveralls
219 | devise
220 | fastercsv
221 | geokit
222 | haml
223 | http_accept_language
224 | nokogiri
225 | pg
226 | puma
227 | rails (~> 4.2.7)
228 | rails_12factor
229 | rails_admin
230 | rubocop
231 | sass-rails (>= 4.0.3)
232 | simplecov
233 | skylight
234 | spring
235 | sqlite3
236 | uglifier
237 | validates_formatting_of
238 | webmock
239 |
240 | RUBY VERSION
241 | ruby 2.3.1p112
242 |
243 | BUNDLED WITH
244 | 1.13.2
245 |
--------------------------------------------------------------------------------
/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 | # The secret key used by Devise. Devise uses this key to generate
5 | # random tokens. Changing this key will render invalid all existing
6 | # confirmation, reset password and unlock tokens in the database.
7 | config.secret_key = 'e642a15001ccf8126bf88426c41497c97d28084fd1de3b2f136ef897257e3e6de525fb751199e439cbefc9c93bc643c59426852c44f4c108dfcb87fdf7cfd503'
8 |
9 | # ==> Mailer Configuration
10 | # Configure the e-mail address which will be shown in Devise::Mailer,
11 | # note that it will be overwritten if you use your own mailer class
12 | # with default "from" parameter.
13 | config.mailer_sender = 'noreply@adoptahydrant.com'
14 |
15 | # Configure the class responsible to send e-mails.
16 | # config.mailer = 'Devise::Mailer'
17 |
18 | # ==> ORM configuration
19 | # Load and configure the ORM. Supports :active_record (default) and
20 | # :mongoid (bson_ext recommended) by default. Other ORMs may be
21 | # available as additional gems.
22 | require 'devise/orm/active_record'
23 |
24 | # ==> Configuration for any authentication mechanism
25 | # Configure which keys are used when authenticating a user. The default is
26 | # just :email. You can configure it to use [:username, :subdomain], so for
27 | # authenticating a user, both parameters are required. Remember that those
28 | # parameters are used only when authenticating and not when retrieving from
29 | # session. If you need permissions, you should implement that in a before filter.
30 | # You can also supply a hash where the value is a boolean determining whether
31 | # or not authentication should be aborted when the value is not present.
32 | # config.authentication_keys = [ :email ]
33 |
34 | # Configure parameters from the request object used for authentication. Each entry
35 | # given should be a request method and it will automatically be passed to the
36 | # find_for_authentication method and considered in your model lookup. For instance,
37 | # if you set :request_keys to [:subdomain], :subdomain will be used on authentication.
38 | # The same considerations mentioned for authentication_keys also apply to request_keys.
39 | # config.request_keys = []
40 |
41 | # Configure which authentication keys should be case-insensitive.
42 | # These keys will be downcased upon creating or modifying a user and when used
43 | # to authenticate or find a user. Default is :email.
44 | config.case_insensitive_keys = [:email]
45 |
46 | # Configure which authentication keys should have whitespace stripped.
47 | # These keys will have whitespace before and after removed upon creating or
48 | # modifying a user and when used to authenticate or find a user. Default is :email.
49 | config.strip_whitespace_keys = [:email]
50 |
51 | # Tell if authentication through request.params is enabled. True by default.
52 | # It can be set to an array that will enable params authentication only for the
53 | # given strategies, for example, `config.params_authenticatable = [:database]` will
54 | # enable it only for database (email + password) authentication.
55 | # config.params_authenticatable = true
56 |
57 | # Tell if authentication through HTTP Auth is enabled. False by default.
58 | # It can be set to an array that will enable http authentication only for the
59 | # given strategies, for example, `config.http_authenticatable = [:database]` will
60 | # enable it only for database authentication. The supported strategies are:
61 | # :database = Support basic authentication with authentication key + password
62 | # config.http_authenticatable = false
63 |
64 | # If http headers should be returned for AJAX requests. True by default.
65 | # config.http_authenticatable_on_xhr = true
66 |
67 | # The realm used in Http Basic Authentication. 'Application' by default.
68 | # config.http_authentication_realm = 'Application'
69 |
70 | # It will change confirmation, password recovery and other workflows
71 | # to behave the same regardless if the e-mail provided was right or wrong.
72 | # Does not affect registerable.
73 | # config.paranoid = true
74 |
75 | # By default Devise will store the user in session. You can skip storage for
76 | # particular strategies by setting this option.
77 | # Notice that if you are skipping storage for all authentication paths, you
78 | # may want to disable generating routes to Devise's sessions controller by
79 | # passing :skip => :sessions to `devise_for` in your config/routes.rb
80 | config.skip_session_storage = [:http_auth]
81 |
82 | # By default, Devise cleans up the CSRF token on authentication to
83 | # avoid CSRF token fixation attacks. This means that, when using AJAX
84 | # requests for sign in and sign up, you need to get a new CSRF token
85 | # from the server. You can disable this option at your own risk.
86 | # config.clean_up_csrf_token_on_authentication = true
87 |
88 | # ==> Configuration for :database_authenticatable
89 | # For bcrypt, this is the cost for hashing the password and defaults to 10. If
90 | # using other encryptors, it sets how many times you want the password re-encrypted.
91 | #
92 | # Limiting the stretches to just one in testing will increase the performance of
93 | # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use
94 | # a value less than 10 in other environments.
95 | config.stretches = Rails.env.test? ? 1 : 10
96 |
97 | # Setup a pepper to generate the encrypted password.
98 | config.pepper = 'd0ce05a602094357144e8d2ce90258904f8cb26fb943cefd6fe0b824752616a9254fadabed3a47ba5c0de66a359513768ab1ab233d9cfef893f376a9b5ebcf68'
99 |
100 | # ==> Configuration for :confirmable
101 | # A period that the user is allowed to access the website even without
102 | # confirming his account. For instance, if set to 2.days, the user will be
103 | # able to access the website for two days without confirming his account,
104 | # access will be blocked just in the third day. Default is 0.days, meaning
105 | # the user cannot access the website without confirming his account.
106 | # config.allow_unconfirmed_access_for = 2.days
107 |
108 | # A period that the user is allowed to confirm their account before their
109 | # token becomes invalid. For example, if set to 3.days, the user can confirm
110 | # their account within 3 days after the mail was sent, but on the fourth day
111 | # their account can't be confirmed with the token any more.
112 | # Default is nil, meaning there is no restriction on how long a user can take
113 | # before confirming their account.
114 | # config.confirm_within = 3.days
115 |
116 | # If true, requires any email changes to be confirmed (exactly the same way as
117 | # initial account confirmation) to be applied. Requires additional unconfirmed_email
118 | # db field (see migrations). Until confirmed new email is stored in
119 | # unconfirmed email column, and copied to email column on successful confirmation.
120 | config.reconfirmable = true
121 |
122 | # Defines which key will be used when confirming an account
123 | # config.confirmation_keys = [ :email ]
124 |
125 | # ==> Configuration for :rememberable
126 | # The time the user will be remembered without asking for credentials again.
127 | config.remember_for = 1.year
128 |
129 | # If true, extends the user's remember period when remembered via cookie.
130 | config.extend_remember_period = true
131 |
132 | # Options to be passed to the created cookie. For instance, you can set
133 | # :secure => true in order to force SSL only cookies.
134 | # config.rememberable_options = {}
135 |
136 | # ==> Configuration for :validatable
137 | # Range for password length. Default is 8..128.
138 | # config.password_length = 8..128
139 |
140 | # Email regex used to validate email formats. It simply asserts that
141 | # one (and only one) @ exists in the given string. This is mainly
142 | # to give user feedback and not to assert the e-mail validity.
143 | # config.email_regexp = /\A[^@]+@[^@]+\z/
144 |
145 | # ==> Configuration for :timeoutable
146 | # The time you want to timeout the user session without activity. After this
147 | # time the user will be asked for credentials again. Default is 30 minutes.
148 | # config.timeout_in = 30.minutes
149 |
150 | # If true, expires auth token on session timeout.
151 | # config.expire_auth_token_on_timeout = false
152 |
153 | # ==> Configuration for :lockable
154 | # Defines which strategy will be used to lock an account.
155 | # :failed_attempts = Locks an account after a number of failed attempts to sign in.
156 | # :none = No lock strategy. You should handle locking by yourself.
157 | # config.lock_strategy = :failed_attempts
158 |
159 | # Defines which key will be used when locking and unlocking an account
160 | # config.unlock_keys = [ :email ]
161 |
162 | # Defines which strategy will be used to unlock an account.
163 | # :email = Sends an unlock link to the user email
164 | # :time = Re-enables login after a certain amount of time (see :unlock_in below)
165 | # :both = Enables both strategies
166 | # :none = No unlock strategy. You should handle unlocking by yourself.
167 | # config.unlock_strategy = :both
168 |
169 | # Number of authentication tries before locking an account if lock_strategy
170 | # is failed attempts.
171 | # config.maximum_attempts = 20
172 |
173 | # Time interval to unlock the account if :time is enabled as unlock_strategy.
174 | # config.unlock_in = 1.hour
175 |
176 | # Warn on the last attempt before the account is locked.
177 | # config.last_attempt_warning = false
178 |
179 | # ==> Configuration for :recoverable
180 | #
181 | # Defines which key will be used when recovering the password for an account
182 | # config.reset_password_keys = [ :email ]
183 |
184 | # Time interval you can reset your password with a reset password key.
185 | # Don't put a too small interval or your users won't have the time to
186 | # change their passwords.
187 | config.reset_password_within = 6.hours
188 |
189 | # ==> Configuration for :encryptable
190 | # Allow you to use another encryption algorithm besides bcrypt (default). You can use
191 | # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1,
192 | # :authlogic_sha512 (then you should set stretches above to 20 for default behavior)
193 | # and :restful_authentication_sha1 (then you should set stretches to 10, and copy
194 | # REST_AUTH_SITE_KEY to pepper).
195 | #
196 | # Require the `devise-encryptable` gem when using anything other than bcrypt
197 | # config.encryptor = :sha512
198 |
199 | # ==> Scopes configuration
200 | # Turn scoped views on. Before rendering "sessions/new", it will first check for
201 | # "users/sessions/new". It's turned off by default because it's slower if you
202 | # are using only default views.
203 | # config.scoped_views = false
204 |
205 | # Configure the default scope given to Warden. By default it's the first
206 | # devise role declared in your routes (usually :user).
207 | # config.default_scope = :user
208 |
209 | # Set this configuration to false if you want /users/sign_out to sign out
210 | # only the current scope. By default, Devise signs out all scopes.
211 | # config.sign_out_all_scopes = true
212 |
213 | # ==> Navigation configuration
214 | # Lists the formats that should be treated as navigational. Formats like
215 | # :html, should redirect to the sign in page when the user does not have
216 | # access, but formats like :xml or :json, should return 401.
217 | #
218 | # If you have any extra navigational formats, like :iphone or :mobile, you
219 | # should add them to the navigational formats lists.
220 | #
221 | # The "*/*" below is required to match Internet Explorer requests.
222 | # config.navigational_formats = ['*/*', :html]
223 |
224 | # The default HTTP method used to sign out a resource. Default is :delete.
225 | config.sign_out_via = :delete
226 |
227 | # ==> OmniAuth
228 | # Add a new OmniAuth provider. Check the wiki for more information on setting
229 | # up on your models and hooks.
230 | # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo'
231 |
232 | # ==> Warden configuration
233 | # If you want to use other strategies, that are not supported by Devise, or
234 | # change the failure app, you can configure them inside the config.warden block.
235 | #
236 | # config.warden do |manager|
237 | # manager.intercept_401 = false
238 | # manager.default_strategies(:scope => :user).unshift :some_external_strategy
239 | # end
240 |
241 | # ==> Mountable engine configurations
242 | # When using Devise inside an engine, let's call it `MyEngine`, and this engine
243 | # is mountable, there are some extra configurations to be taken into account.
244 | # The following options are available, assuming the engine is mounted as:
245 | #
246 | # mount MyEngine, at: '/my_engine'
247 | #
248 | # The router that invoked `devise_for`, in the example above, would be:
249 | # config.router_name = :my_engine
250 | #
251 | # When using omniauth, Devise cannot automatically set Omniauth path,
252 | # so you need to do it manually. For the users scope, it would be:
253 | # config.omniauth_path_prefix = '/my_engine/users/auth'
254 | end
255 |
--------------------------------------------------------------------------------
/app/views/sidebar/_tos.html.haml:
--------------------------------------------------------------------------------
1 | .modal.hide.fade#tos{:style => "display: none;"}
2 | .modal-header
3 | %a{:class => "close", :"data-dismiss" => "modal", :href => "#"}
4 | ×
5 | %h2
6 | = t("titles.tos")
7 | .modal-body
8 | %ol
9 | %li
10 | %h3
11 | Acceptance of Terms
12 | %p
13 | The City of Boston ("City") provides the Adopt-a-Hydrant program ("AAH") to you subject to the following Terms of Service Agreement ("Agreement") which may be updated by us from time to time without notice to you. By accessing and using AAH, you accept and agree to be bound by the terms and provision of this Agreement.
14 | %li
15 | %h3
16 | Description of Adopt-a-Hydrant
17 | %p
18 | Adopt-a-Hydrant is an initiative of the City to encourage residents to shovel out snowed-in fire hydrants. The AAH website allows users to "adopt" a fire hydrant, or state that you intend to help shovel that fire hydrant out if it gets covered by snow.
19 | %p
20 | Unless explicitly stated otherwise, any new features that augment or enhance AAH are covered by this Agreement.
21 | %li
22 | %h3
23 | Obligations
24 | %p
25 | If you choose to adopt a fire hydrant using the AAH website, you are under no obligation and have no responsibility to actually shovel out that fire hydrant or any fire hydrant.
26 | %li
27 | %h3
28 | Termination of Obligations
29 | %p
30 | If you provide any information that is untrue, inaccurate, offensive, not current, or incomplete, or the City has reasonable grounds to suspect that such information is untrue, inaccurate, offensive, not current, or incomplete, the City has the right to suspend or terminate your account and refuse any and all current or future use of the AAH services (or any portion thereof).
31 | %p
32 | Accounts that consistently adopt fire hydrants without shoveling them out may also be terminated.
33 | %p
34 | Additionally, you agree that the City may, without prior notice, immediately terminate, limit your access to, or suspend your AAH service. Cause for such termination, limitation of access, or suspension shall include but not be limited to:
35 | %ol.alpha
36 | %li
37 | breaches or violations of this Agreement or other incorporated agreements or guidelines,
38 | %li
39 | requests by law enforcement or other government agencies,
40 | %li
41 | discontinuance or material modification to AAH (or any part thereof),
42 | %li
43 | unexpected technical or security issues or problems,
44 | %li
45 | extended periods of inactivity,
46 | %li
47 | engagement by you in fraudulent or illegal activities.
48 | %p
49 | Further, you agree that all terminations, limitations of access, and suspensions for cause shall be made in the City's sole discretion and that the City shall not be liable to you or any third party for any termination of your account, any associated email address, or access to AAH.
50 | %li
51 | %h3
52 | City Public Records Policy
53 | %p
54 | Registration data, as well as data you submit using AAH, are subject to all applicable City, State and Federal public records laws.
55 | %li
56 | %h3
57 | Member Conduct
58 | %p
59 | You understand that all information entered by the user is the sole responsibility of the person from whom such Content originated. This means that you, and not the City, are entirely responsible for all Content that you submit or otherwise make available via AAH. The City does not control the sign-in content posted and, as such, does not guarantee the accuracy, integrity, or quality of such Content. You understand that by using AAH, you may be exposed to Content that is offensive, indecent, or objectionable. Under no circumstances will the City be liable in any way for any Content, including, but not limited to, any errors or omissions in any Content, or any loss or damage of any kind incurred as a result of the use of any Content submitted or otherwise made available via AAH.
60 | %p
61 | We are under no obligation to enforce this Agreement on your behalf against another user. While we encourage you to let us know if you believe another user violated this Agreement, we reserve the right to investigate and take appropriate action at the City's sole discretion.
62 | %p
63 | You agree to not use AAH to:
64 | %ol.alpha
65 | %li
66 | submit Content that is false or inaccurate;
67 | %li
68 | submit Content that does not generally pertain to the designated topic or theme;
69 | %li
70 | submit or otherwise make available any Content that is unlawful, harmful, threatening, abusive, harassing, tortuous, defamatory, vulgar, obscene, libelous, invasive of another's privacy, hateful, or racially, ethnically, or otherwise objectionable;
71 | %li
72 | harm minors in any way;
73 | %li
74 | impersonate any person or entity, or falsely state or otherwise misrepresent your affiliation with a person or entity;
75 | %li
76 | submit or otherwise make available any Content that you do not have a right to make available under any law or under contractual or fiduciary relationships (such as inside information, proprietary and confidential information learned or disclosed as part of employment relationships or under nondisclosure agreements);
77 | %li
78 | submit or otherwise make available any Content that infringes any patent, trademark, trade secret, copyright, or other proprietary rights ("Rights") of any party;
79 | %li
80 | submit or otherwise make available any unsolicited or unauthorized advertising, promotional materials, "junk mail," "spam," "chain letters," "pyramid schemes," or any other form of communication not relevant to specific City service requests;
81 | %li
82 | submit or otherwise make available any material that contains software viruses or any other computer code, files or programs designed to interrupt, destroy or limit the functionality of any computer software or hardware or telecommunications equipment;
83 | %li
84 | intentionally or unintentionally violate any applicable local, state, national or international law;
85 | %li
86 | "stalk" or otherwise harass another; or
87 | %li
88 | collect or store personal data about other users in connection with the prohibited conduct and activities set forth in paragraphs (a) through (k) above.
89 | %p
90 | You acknowledge that the City may or may not pre-screen Content that is shared, but that the City and its designees shall have the right (but not the obligation) in their sole discretion to pre-screen, refuse, or remove any Content that is available via AAH. Without limiting the foregoing, the City and its designees shall have the right to remove any Content that violates this Agreement or is otherwise objectionable. You agree that you must evaluate, and bear all risks associated with, the use of any Content, including any reliance on the accuracy, completeness, or usefulness of such Content.
91 | %p
92 | You acknowledge, consent and agree that the City may access, preserve and disclose your account information and Content if required to do so by law or in a good faith belief that such access preservation or disclosure is reasonably necessary to:
93 | %ol.roman
94 | %li
95 | comply with legal process;
96 | %li
97 | enforce this Agreement;
98 | %li
99 | respond to claims that any Content violates the rights of third parties;
100 | %li
101 | respond to your requests for customer service;
102 | %li
103 | protect the rights, property or personal safety of the City, its users and the public.
104 | %p
105 | You may not attempt to override or circumvent any of the usage rules embedded into AAH.
106 | %p
107 | You may not trace any information of any other AAH user or visitor or otherwise use AAH for the purpose of obtaining information of any other AAH user.
108 | %p
109 | You may not attempt to gain unauthorized access to AAH or the computer systems and networks connected to AAH through hacking, password mining, or any other means.
110 | %p
111 | You may not release the results of any performance or functional evaluation of AAH to any third party without prior written approval of the City for each such release.
112 | %p
113 | You may not take any action that imposes, or may impose, in the City's sole discretion, an unreasonable or disproportionately large load on the City's technology infrastructure or otherwise make excessive traffic demands of AAH.
114 | %li
115 | %h3
116 | Interstate Nature of Communications on Adopt-a-Hydrant
117 | %p
118 | When you use the application, you acknowledge that in using AAH, you will be causing communications to be sent potentially through a variety of networks (Internet service provider, wireless phone network, etc.) As a result, even communications that seem to be intrastate in nature can result in the transmission of interstate communications regardless of where you are physically located at the time of transmission. Accordingly, you agree that use of the AAH may result in interstate data transmissions.
119 | %li
120 | %h3
121 | Submissions to the City
122 | %p
123 | By submitting feedback ("Submissions") to the City through the AAH feedback function, you acknowledge and agree that:
124 | %ol.alpha
125 | %li
126 | your Submissions do not contain confidential or proprietary information;
127 | %li
128 | the City is not under any obligation of confidentiality, express or implied, with respect to the Submissions;
129 | %li
130 | the City shall be entitled to use or disclose (or choose not to use or disclose) such Submissions for any purpose, in any way, in any media worldwide;
131 | %li
132 | the City may have something similar to the Submissions already under consideration or in development;
133 | %li
134 | your Submissions automatically become the property of the City without any obligation of the City to you;
135 | %li
136 | you are not entitled to any compensation or reimbursement of any kind from the City under any circumstances.
137 | %li
138 | %h3
139 | Indemnity
140 | %p
141 | You agree to indemnify and hold the City and its subsidiaries, affiliates, officers, agents, employees, partners, and licensors harmless from any claim or demand (including reasonable attorneys' fees) made by any third party due to or arising out of:
142 | %ol.alpha
143 | %li
144 | Content you submit or otherwise make available through AAH,
145 | %li
146 | your use of AAH,
147 | %li
148 | your connection to AAH,
149 | %li
150 | your violation of this Agreement,
151 | %li
152 | your violation of any Rights of another.
153 | %li
154 | %h3
155 | Modifications to Adopt-a-Hydrant
156 | %p
157 | The City reserves the right at any time and from time to time to modify or discontinue, temporarily or permanently, AAH (or any part thereof) with or without notice. You agree that the City shall not be liable to you or to any third party for any modification, suspension or discontinuance of the AAH (or any part thereof).
158 | %li
159 | %h3
160 | Links
161 | %p
162 | The City may provide links to other World Wide Web sites or resources. You acknowledge and agree that the City is not responsible for the availability of such external sites or resources.
163 | %p
164 | AAH may facilitate your use of third party services not provided by the City ("Third Party Services"). The City makes no representations or warranties regarding the performance of such Third Party Services, their compliance with applicable laws and regulations, or any other aspects of such Third Party Services. Your use of Third Party Services is at your own risk and you are solely responsible for complying with all legal and contractual requirements necessary for using Third Party Services.
165 | %li
166 | %h3
167 | The City's Proprietary Rights
168 | %p
169 | You acknowledge and agree that AAH contains proprietary and confidential information that is owned by the City and protected by applicable intellectual property and other laws. You will not use such proprietary information in any way whatsoever except for use of AAH in compliance with the provisions of this Agreement.
170 | %h4.upcase
171 | Disclaimer of Warranties
172 | %p.upcase
173 | You expressly understand and agree that:
174 | %ol.alpha.upcase
175 | %li
176 | Your use of AAH is at your sole risk. The application is provided on an "as is" and "as available" basis. The city expressly disclaims all warranties of any kind, whether express or implied, including, but not limited to the implied warranties of title, merchantability, fitness for a particular purpose and non-infringement.
177 | %li
178 | The city and its subsidiaries, affiliates, officers, employees, agents, partners, and licensors make no warrany that:
179 | %ol.roman
180 | %li
181 | AAH will meet your requirements;
182 | %li
183 | AAH will be uninterrupted, timely, secore, or error-free;
184 | %li
185 | the results that may be obtained from the use of AAH will be accurate or reliable;
186 | %li
187 | any errors in the software will be corrected;
188 | %li
189 | AAH will be free from corruption, viruses, hacking, or other security intrusion.
190 | %li
191 | %h3
192 | Limitation of Liability
193 | %p.upcase
194 | You expressly understand and agree that the city shall not be liable to you for any punitive, indirect, incidental, special, consequential, or exemplary damages, including, but not limtied to, damages for loss of profits, goodwill, use, data, or other intangible losses resulting from:
195 | %ol.alpha.upcase
196 | %li
197 | Your access to or use of or inability to access or use the services;
198 | %li
199 | any conduct or content or any third party on the services, including without limitation, any defamatory, offensive or illegal conduct of other users or third parties;
200 | %li
201 | any content obtained from the services;
202 | %li
203 | unauthorized access, use or alteration of your transmissions or content, whether based on warranty, contract, tort (including negligence) or any other legal theory, whether or not the city has been informed of the possibility of such damage, and even if a remedy set forth herein is found to have failed of its essential purpose.
204 | %p.upcase
205 | You expressly agree that the city is not responsible for any contact or interaction between you and any other user of AAH and that you bear the sole risk of transmitting through the application any content, including information which identifies you or your location.
206 | %p.upcase
207 | You agree that the negation of damages set forth above is a fundamental element of the basis of the bargain between the city and you and that the foregoing limitation of liability is an agreed upon allocation of risk between you and the city. You acknowledge that absent your agreement to this limitation of liability the city would not provide AAH to you.
208 | %li
209 | %h3
210 | Notice
211 | %p
212 | The City may provide you with notices regarding AAH, including changes to this Agreement, by email, SMS, text message, postings on AAH, or any other reasonable means now known or hereafter developed. Notices shall be effective immediately.
213 | %p
214 | Such notices may not be received if you violate this Agreement by accessing the AAH in an unauthorized manner. You agree that you will be deemed to have received any and all notices that would have been delivered had you accessed AAH in an authorized manner.
215 | %li
216 | %h3
217 | Minor
218 | %p
219 | You represent that you are 13 years of age or older and, if you are under the age of 18, you either are an emancipated minor, or have obtained the legal consent of your parent or legal guardian to accept and agree to be bound by the terms and provisions of this Agreement.
220 | %p
221 | If you have agreed to allow your minor child, or a child for whom you are legal guardian ("Minor"), to use AAH, you agree that you shall be solely responsible for:
222 | %ol.alpha
223 | %li
224 | the online conduct of such Minor,
225 | %li
226 | monitoring such Minor’s access to and use of AAH, and
227 | %li
228 | the consequences of any use of AAH by such Minor.
229 | %li
230 | %h3
231 | Governing Law and Venue
232 | %p
233 | This Agreement is governed by the laws of the Commonwealth of Massachusetts without regard to any conflict of law provisions. You agree that any claim or dispute with the City relating in any way to your use of AAH shall be brought exclusively before a state or federal court sitting in Boston, Massachusetts, and you irrevocably waive any jurisdictional, venue, or inconvenient forum objections to such courts.
234 | %li
235 | %h3
236 | Miscellaneous
237 | %p
238 | This Agreement constitutes the entire agreement between you and the City with respect to your use of AAH, superseding any prior version of this Agreement between you and the City with respect to AAH.
239 | %p
240 | If any provision of this Agreement shall be unlawful, void, or unenforceable, then that provision shall be deemed severed from this Agreement and shall not affect the validity or enforceability of the remaining provisions of this Agreement.
241 | %p
242 | The City's failure to assert any right or provision under this Agreement shall not constitute a waiver of such right or provision.
243 | %p
244 | You agree that any cause of action arising out of or related to your use of AAH must commence within one year after the cause of action accrues. Otherwise, such cause of action is permanently barred.
245 | %p
246 | The section titles in this Agreement are for convenience only and have no legal or contractual effect.
247 | %p
248 | Last updated August 10, 2011.
249 |
--------------------------------------------------------------------------------
/test/fixtures/city_hall.json:
--------------------------------------------------------------------------------
1 | {
2 | "results" : [
3 | {
4 | "address_components" : [
5 | {
6 | "long_name" : "Boston City Hall",
7 | "short_name" : "Boston City Hall",
8 | "types" : [ "point_of_interest", "establishment" ]
9 | },
10 | {
11 | "long_name" : "1",
12 | "short_name" : "1",
13 | "types" : [ "street_number" ]
14 | },
15 | {
16 | "long_name" : "City Hall Square",
17 | "short_name" : "City Hall Square",
18 | "types" : [ "route" ]
19 | },
20 | {
21 | "long_name" : "Downtown",
22 | "short_name" : "Downtown",
23 | "types" : [ "neighborhood", "political" ]
24 | },
25 | {
26 | "long_name" : "Boston",
27 | "short_name" : "Boston",
28 | "types" : [ "locality", "political" ]
29 | },
30 | {
31 | "long_name" : "Suffolk",
32 | "short_name" : "Suffolk",
33 | "types" : [ "administrative_area_level_2", "political" ]
34 | },
35 | {
36 | "long_name" : "Massachusetts",
37 | "short_name" : "MA",
38 | "types" : [ "administrative_area_level_1", "political" ]
39 | },
40 | {
41 | "long_name" : "United States",
42 | "short_name" : "US",
43 | "types" : [ "country", "political" ]
44 | },
45 | {
46 | "long_name" : "02201",
47 | "short_name" : "02201",
48 | "types" : [ "postal_code" ]
49 | },
50 | {
51 | "long_name" : "1001",
52 | "short_name" : "1001",
53 | "types" : []
54 | }
55 | ],
56 | "formatted_address" : "Boston City Hall, 1 City Hall Square, Boston, MA 02201, USA",
57 | "geometry" : {
58 | "location" : {
59 | "lat" : 42.360406,
60 | "lng" : -71.057993
61 | },
62 | "location_type" : "APPROXIMATE",
63 | "viewport" : {
64 | "northeast" : {
65 | "lat" : 42.3686503,
66 | "lng" : -71.0419856
67 | },
68 | "southwest" : {
69 | "lat" : 42.3521606,
70 | "lng" : -71.0740004
71 | }
72 | }
73 | },
74 | "partial_match" : true,
75 | "types" : [
76 | "city_hall",
77 | "point_of_interest",
78 | "local_government_office",
79 | "locality",
80 | "political",
81 | "establishment"
82 | ]
83 | },
84 | {
85 | "address_components" : [
86 | {
87 | "long_name" : "Faneuil Hall",
88 | "short_name" : "Faneuil Hall",
89 | "types" : [ "point_of_interest", "establishment" ]
90 | },
91 | {
92 | "long_name" : "Boston National Historical Park",
93 | "short_name" : "Boston National Historical Park",
94 | "types" : [ "establishment" ]
95 | },
96 | {
97 | "long_name" : "1",
98 | "short_name" : "1",
99 | "types" : [ "street_number" ]
100 | },
101 | {
102 | "long_name" : "Faneuil Hall Square",
103 | "short_name" : "Faneuil Hall Square",
104 | "types" : [ "route" ]
105 | },
106 | {
107 | "long_name" : "Downtown",
108 | "short_name" : "Downtown",
109 | "types" : [ "neighborhood", "political" ]
110 | },
111 | {
112 | "long_name" : "Boston",
113 | "short_name" : "Boston",
114 | "types" : [ "locality", "political" ]
115 | },
116 | {
117 | "long_name" : "Boston",
118 | "short_name" : "Boston",
119 | "types" : [ "administrative_area_level_3", "political" ]
120 | },
121 | {
122 | "long_name" : "Suffolk",
123 | "short_name" : "Suffolk",
124 | "types" : [ "administrative_area_level_2", "political" ]
125 | },
126 | {
127 | "long_name" : "Massachusetts",
128 | "short_name" : "MA",
129 | "types" : [ "administrative_area_level_1", "political" ]
130 | },
131 | {
132 | "long_name" : "United States",
133 | "short_name" : "US",
134 | "types" : [ "country", "political" ]
135 | },
136 | {
137 | "long_name" : "02109",
138 | "short_name" : "02109",
139 | "types" : [ "postal_code" ]
140 | }
141 | ],
142 | "formatted_address" : "Faneuil Hall, Boston National Historical Park, 1 Faneuil Hall Square, Boston, MA 02109, USA",
143 | "geometry" : {
144 | "location" : {
145 | "lat" : 42.3600619,
146 | "lng" : -71.05610299999999
147 | },
148 | "location_type" : "APPROXIMATE",
149 | "viewport" : {
150 | "northeast" : {
151 | "lat" : 42.36141088029149,
152 | "lng" : -71.05475401970848
153 | },
154 | "southwest" : {
155 | "lat" : 42.3587129197085,
156 | "lng" : -71.0574519802915
157 | }
158 | }
159 | },
160 | "partial_match" : true,
161 | "types" : [ "point_of_interest", "establishment" ]
162 | },
163 | {
164 | "address_components" : [
165 | {
166 | "long_name" : "Hall Street",
167 | "short_name" : "Hall St",
168 | "types" : [ "route" ]
169 | },
170 | {
171 | "long_name" : "Jamaica Plain",
172 | "short_name" : "Jamaica Plain",
173 | "types" : [ "sublocality", "political" ]
174 | },
175 | {
176 | "long_name" : "Boston",
177 | "short_name" : "Boston",
178 | "types" : [ "locality", "political" ]
179 | },
180 | {
181 | "long_name" : "Suffolk",
182 | "short_name" : "Suffolk",
183 | "types" : [ "administrative_area_level_2", "political" ]
184 | },
185 | {
186 | "long_name" : "Massachusetts",
187 | "short_name" : "MA",
188 | "types" : [ "administrative_area_level_1", "political" ]
189 | },
190 | {
191 | "long_name" : "United States",
192 | "short_name" : "US",
193 | "types" : [ "country", "political" ]
194 | },
195 | {
196 | "long_name" : "02130",
197 | "short_name" : "02130",
198 | "types" : [ "postal_code" ]
199 | }
200 | ],
201 | "formatted_address" : "Hall Street, Boston, MA 02130, USA",
202 | "geometry" : {
203 | "bounds" : {
204 | "northeast" : {
205 | "lat" : 42.3054659,
206 | "lng" : -71.1118637
207 | },
208 | "southwest" : {
209 | "lat" : 42.3046598,
210 | "lng" : -71.1146641
211 | }
212 | },
213 | "location" : {
214 | "lat" : 42.3048254,
215 | "lng" : -71.1134469
216 | },
217 | "location_type" : "GEOMETRIC_CENTER",
218 | "viewport" : {
219 | "northeast" : {
220 | "lat" : 42.3064118302915,
221 | "lng" : -71.1118637
222 | },
223 | "southwest" : {
224 | "lat" : 42.3037138697085,
225 | "lng" : -71.1146641
226 | }
227 | }
228 | },
229 | "partial_match" : true,
230 | "types" : [ "route" ]
231 | },
232 | {
233 | "address_components" : [
234 | {
235 | "long_name" : "City Hall Square",
236 | "short_name" : "City Hall Square",
237 | "types" : [ "route" ]
238 | },
239 | {
240 | "long_name" : "Downtown",
241 | "short_name" : "Downtown",
242 | "types" : [ "neighborhood", "political" ]
243 | },
244 | {
245 | "long_name" : "Boston",
246 | "short_name" : "Boston",
247 | "types" : [ "locality", "political" ]
248 | },
249 | {
250 | "long_name" : "Suffolk",
251 | "short_name" : "Suffolk",
252 | "types" : [ "administrative_area_level_2", "political" ]
253 | },
254 | {
255 | "long_name" : "Massachusetts",
256 | "short_name" : "MA",
257 | "types" : [ "administrative_area_level_1", "political" ]
258 | },
259 | {
260 | "long_name" : "United States",
261 | "short_name" : "US",
262 | "types" : [ "country", "political" ]
263 | }
264 | ],
265 | "formatted_address" : "City Hall Square, Boston, MA, USA",
266 | "geometry" : {
267 | "bounds" : {
268 | "northeast" : {
269 | "lat" : 42.360398,
270 | "lng" : -71.05729599999999
271 | },
272 | "southwest" : {
273 | "lat" : 42.3579646,
274 | "lng" : -71.0593976
275 | }
276 | },
277 | "location" : {
278 | "lat" : 42.360248,
279 | "lng" : -71.0574544
280 | },
281 | "location_type" : "GEOMETRIC_CENTER",
282 | "viewport" : {
283 | "northeast" : {
284 | "lat" : 42.3605302802915,
285 | "lng" : -71.05699781970849
286 | },
287 | "southwest" : {
288 | "lat" : 42.3578323197085,
289 | "lng" : -71.05969578029149
290 | }
291 | }
292 | },
293 | "partial_match" : true,
294 | "types" : [ "route" ]
295 | },
296 | {
297 | "address_components" : [
298 | {
299 | "long_name" : "Newhall Street",
300 | "short_name" : "Newhall St",
301 | "types" : [ "route" ]
302 | },
303 | {
304 | "long_name" : "Dorchester",
305 | "short_name" : "Dorchester",
306 | "types" : [ "neighborhood", "political" ]
307 | },
308 | {
309 | "long_name" : "Boston",
310 | "short_name" : "Boston",
311 | "types" : [ "locality", "political" ]
312 | },
313 | {
314 | "long_name" : "Suffolk",
315 | "short_name" : "Suffolk",
316 | "types" : [ "administrative_area_level_2", "political" ]
317 | },
318 | {
319 | "long_name" : "Massachusetts",
320 | "short_name" : "MA",
321 | "types" : [ "administrative_area_level_1", "political" ]
322 | },
323 | {
324 | "long_name" : "United States",
325 | "short_name" : "US",
326 | "types" : [ "country", "political" ]
327 | },
328 | {
329 | "long_name" : "02122",
330 | "short_name" : "02122",
331 | "types" : [ "postal_code" ]
332 | }
333 | ],
334 | "formatted_address" : "Newhall Street, Boston, MA 02122, USA",
335 | "geometry" : {
336 | "bounds" : {
337 | "northeast" : {
338 | "lat" : 42.2893203,
339 | "lng" : -71.050439
340 | },
341 | "southwest" : {
342 | "lat" : 42.2877758,
343 | "lng" : -71.051587
344 | }
345 | },
346 | "location" : {
347 | "lat" : 42.2886742,
348 | "lng" : -71.05117170000001
349 | },
350 | "location_type" : "GEOMETRIC_CENTER",
351 | "viewport" : {
352 | "northeast" : {
353 | "lat" : 42.2898970302915,
354 | "lng" : -71.04966401970849
355 | },
356 | "southwest" : {
357 | "lat" : 42.2871990697085,
358 | "lng" : -71.05236198029149
359 | }
360 | }
361 | },
362 | "partial_match" : true,
363 | "types" : [ "route" ]
364 | },
365 | {
366 | "address_components" : [
367 | {
368 | "long_name" : "City Hall Avenue",
369 | "short_name" : "City Hall Avenue",
370 | "types" : [ "route" ]
371 | },
372 | {
373 | "long_name" : "Downtown",
374 | "short_name" : "Downtown",
375 | "types" : [ "neighborhood", "political" ]
376 | },
377 | {
378 | "long_name" : "Boston",
379 | "short_name" : "Boston",
380 | "types" : [ "locality", "political" ]
381 | },
382 | {
383 | "long_name" : "Suffolk",
384 | "short_name" : "Suffolk",
385 | "types" : [ "administrative_area_level_2", "political" ]
386 | },
387 | {
388 | "long_name" : "Massachusetts",
389 | "short_name" : "MA",
390 | "types" : [ "administrative_area_level_1", "political" ]
391 | },
392 | {
393 | "long_name" : "United States",
394 | "short_name" : "US",
395 | "types" : [ "country", "political" ]
396 | }
397 | ],
398 | "formatted_address" : "City Hall Avenue, Boston, MA, USA",
399 | "geometry" : {
400 | "bounds" : {
401 | "northeast" : {
402 | "lat" : 42.3582279,
403 | "lng" : -71.058887
404 | },
405 | "southwest" : {
406 | "lat" : 42.357693,
407 | "lng" : -71.0593676
408 | }
409 | },
410 | "location" : {
411 | "lat" : 42.358086,
412 | "lng" : -71.05902329999999
413 | },
414 | "location_type" : "GEOMETRIC_CENTER",
415 | "viewport" : {
416 | "northeast" : {
417 | "lat" : 42.3593094302915,
418 | "lng" : -71.05777831970849
419 | },
420 | "southwest" : {
421 | "lat" : 42.3566114697085,
422 | "lng" : -71.0604762802915
423 | }
424 | }
425 | },
426 | "partial_match" : true,
427 | "types" : [ "route" ]
428 | },
429 | {
430 | "address_components" : [
431 | {
432 | "long_name" : "Hall Place",
433 | "short_name" : "Hall Pl",
434 | "types" : [ "route" ]
435 | },
436 | {
437 | "long_name" : "Telegraph Hill",
438 | "short_name" : "Telegraph Hill",
439 | "types" : [ "neighborhood", "political" ]
440 | },
441 | {
442 | "long_name" : "Boston",
443 | "short_name" : "Boston",
444 | "types" : [ "locality", "political" ]
445 | },
446 | {
447 | "long_name" : "Suffolk",
448 | "short_name" : "Suffolk",
449 | "types" : [ "administrative_area_level_2", "political" ]
450 | },
451 | {
452 | "long_name" : "Massachusetts",
453 | "short_name" : "MA",
454 | "types" : [ "administrative_area_level_1", "political" ]
455 | },
456 | {
457 | "long_name" : "United States",
458 | "short_name" : "US",
459 | "types" : [ "country", "political" ]
460 | },
461 | {
462 | "long_name" : "02127",
463 | "short_name" : "02127",
464 | "types" : [ "postal_code" ]
465 | }
466 | ],
467 | "formatted_address" : "Hall Place, Boston, MA 02127, USA",
468 | "geometry" : {
469 | "bounds" : {
470 | "northeast" : {
471 | "lat" : 42.3339539,
472 | "lng" : -71.034485
473 | },
474 | "southwest" : {
475 | "lat" : 42.3335792,
476 | "lng" : -71.0347695
477 | }
478 | },
479 | "location" : {
480 | "lat" : 42.3336703,
481 | "lng" : -71.0347611
482 | },
483 | "location_type" : "GEOMETRIC_CENTER",
484 | "viewport" : {
485 | "northeast" : {
486 | "lat" : 42.3351155302915,
487 | "lng" : -71.0332782697085
488 | },
489 | "southwest" : {
490 | "lat" : 42.3324175697085,
491 | "lng" : -71.03597623029151
492 | }
493 | }
494 | },
495 | "partial_match" : true,
496 | "types" : [ "route" ]
497 | },
498 | {
499 | "address_components" : [
500 | {
501 | "long_name" : "Hall Avenue",
502 | "short_name" : "Hall Ave",
503 | "types" : [ "route" ]
504 | },
505 | {
506 | "long_name" : "Dudley / Brunswick King",
507 | "short_name" : "Dudley / Brunswick King",
508 | "types" : [ "neighborhood", "political" ]
509 | },
510 | {
511 | "long_name" : "Roxbury",
512 | "short_name" : "Roxbury",
513 | "types" : [ "sublocality", "political" ]
514 | },
515 | {
516 | "long_name" : "Boston",
517 | "short_name" : "Boston",
518 | "types" : [ "locality", "political" ]
519 | },
520 | {
521 | "long_name" : "Suffolk",
522 | "short_name" : "Suffolk",
523 | "types" : [ "administrative_area_level_2", "political" ]
524 | },
525 | {
526 | "long_name" : "Massachusetts",
527 | "short_name" : "MA",
528 | "types" : [ "administrative_area_level_1", "political" ]
529 | },
530 | {
531 | "long_name" : "United States",
532 | "short_name" : "US",
533 | "types" : [ "country", "political" ]
534 | },
535 | {
536 | "long_name" : "02121",
537 | "short_name" : "02121",
538 | "types" : [ "postal_code" ]
539 | }
540 | ],
541 | "formatted_address" : "Hall Avenue, Boston, MA 02121, USA",
542 | "geometry" : {
543 | "bounds" : {
544 | "northeast" : {
545 | "lat" : 42.3084888,
546 | "lng" : -71.082103
547 | },
548 | "southwest" : {
549 | "lat" : 42.3082657,
550 | "lng" : -71.0821694
551 | }
552 | },
553 | "location" : {
554 | "lat" : 42.30838,
555 | "lng" : -71.0821347
556 | },
557 | "location_type" : "GEOMETRIC_CENTER",
558 | "viewport" : {
559 | "northeast" : {
560 | "lat" : 42.3097262302915,
561 | "lng" : -71.0807872197085
562 | },
563 | "southwest" : {
564 | "lat" : 42.3070282697085,
565 | "lng" : -71.0834851802915
566 | }
567 | }
568 | },
569 | "partial_match" : true,
570 | "types" : [ "route" ]
571 | }
572 | ],
573 | "status" : "OK"
574 | }
575 |
--------------------------------------------------------------------------------
/app/assets/javascripts/main.js.erb:
--------------------------------------------------------------------------------
1 | $(function() {
2 | var center = new google.maps.LatLng(42.358431, -71.059773);
3 | var mapOptions = {
4 | center: center,
5 | disableDoubleClickZoom: true,
6 | keyboardShortcuts: false,
7 | mapTypeControl: false,
8 | mapTypeId: google.maps.MapTypeId.ROADMAP,
9 | maxZoom: 19,
10 | minZoom: 15,
11 | panControl: false,
12 | rotateControl: false,
13 | scaleControl: false,
14 | scrollwheel: false,
15 | streetViewControl: true,
16 | zoom: 15,
17 | zoomControl: true
18 | };
19 | var map = new google.maps.Map(document.getElementById("map"), mapOptions);
20 | var size = new google.maps.Size(27.0, 37.0);
21 | var origin = new google.maps.Point(0, 0);
22 | var anchor = new google.maps.Point(13.0, 18.0);
23 | var greenMarkerImage = new google.maps.MarkerImage('<%= image_path 'markers/green.png' %>',
24 | size,
25 | origin,
26 | anchor
27 | );
28 | var redMarkerImage = new google.maps.MarkerImage('<%= image_path 'markers/red.png' %>',
29 | size,
30 | origin,
31 | anchor
32 | );
33 | var markerShadowImage = new google.maps.MarkerImage('<%= image_path 'markers/shadow.png' %>',
34 | new google.maps.Size(46.0, 37.0),
35 | origin,
36 | anchor
37 | );
38 | var activeThingId;
39 | var activeMarker;
40 | var activeInfoWindow;
41 | var isWindowOpen = false;
42 | var thingIds = [];
43 | function addMarker(thingId, point, color) {
44 | if(color === 'green') {
45 | var image = greenMarkerImage;
46 | } else if(color === 'red') {
47 | var image = redMarkerImage;
48 | }
49 | var marker = new google.maps.Marker({
50 | animation: google.maps.Animation.DROP,
51 | icon: image,
52 | map: map,
53 | position: point,
54 | shadow: markerShadowImage
55 | });
56 | google.maps.event.addListener(marker, 'click', function() {
57 | if(activeInfoWindow) {
58 | activeInfoWindow.close();
59 | }
60 | var infoWindow = new google.maps.InfoWindow({
61 | maxWidth: 210
62 | });
63 | google.maps.event.addListener(infoWindow, 'closeclick', function() {
64 | isWindowOpen = false;
65 | });
66 | activeInfoWindow = infoWindow;
67 | activeThingId = thingId;
68 | activeMarker = marker;
69 | $.ajax({
70 | type: 'GET',
71 | url: '/info_window',
72 | data: {
73 | 'thing_id': thingId
74 | },
75 | success: function(data) {
76 | // Prevent race condition, which could lead to multiple windows being open at the same time.
77 | if(infoWindow === activeInfoWindow) {
78 | infoWindow.setContent(data);
79 | infoWindow.open(map, marker);
80 | isWindowOpen = true;
81 | }
82 | }
83 | });
84 | });
85 | thingIds.push(thingId);
86 | }
87 | function addMarkersAround(lat, lng) {
88 | var submitButton = $("#address_form input[type='submit']");
89 | $.ajax({
90 | type: 'GET',
91 | url: '/things.json',
92 | data: {
93 | 'utf8': '✓',
94 | 'lat': lat,
95 | 'lng': lng,
96 | 'limit': $('#address_form input[name="limit"]').val()
97 | },
98 | error: function(jqXHR) {
99 | $(submitButton).attr("disabled", false);
100 | },
101 | success: function(data) {
102 | $(submitButton).attr("disabled", false);
103 | if(data.errors) {
104 | $('#address').parent().addClass('error');
105 | $('#address').focus();
106 | } else {
107 | $('#address').parent().removeClass('error');
108 | var i = -1;
109 | $(data).each(function(index, thing) {
110 | if($.inArray(thing.id, thingIds) === -1) {
111 | i += 1;
112 | } else {
113 | // continue
114 | return true;
115 | }
116 | setTimeout(function() {
117 | var point = new google.maps.LatLng(thing.lat, thing.lng);
118 | if(thing.user_id) {
119 | var color = 'green';
120 | } else {
121 | var color = 'red';
122 | }
123 | addMarker(thing.id, point, color);
124 | }, i * 100);
125 | });
126 | }
127 | }
128 | });
129 | }
130 | google.maps.event.addListener(map, 'idle', function() {
131 | var center = map.getCenter();
132 | addMarkersAround(center.lat(), center.lng());
133 | });
134 | $('#address_form').live('submit', function() {
135 | var submitButton = $("#address_form input[type='submit']");
136 | $(submitButton).attr("disabled", true);
137 | if($('#address').val() === '') {
138 | $(submitButton).attr("disabled", false);
139 | $('#address').parent().addClass('error');
140 | $('#address').focus();
141 | } else {
142 | $.ajax({
143 | type: 'GET',
144 | url: '/address.json',
145 | data: {
146 | 'utf8': '✓',
147 | 'city_state': $('#city_state').val(),
148 | 'address': $('#address').val()
149 | },
150 | error: function(jqXHR) {
151 | $(submitButton).attr("disabled", false);
152 | $('#address').parent().addClass('error');
153 | $('#address').focus();
154 | },
155 | success: function(data) {
156 | $(submitButton).attr("disabled", false);
157 | if(data.errors) {
158 | $('#address').parent().addClass('error');
159 | $('#address').focus();
160 | } else {
161 | $('#address').parent().removeClass('error');
162 | addMarkersAround(data[0], data[1]);
163 | var center = new google.maps.LatLng(data[0], data[1]);
164 | map.setCenter(center);
165 | map.setZoom(19);
166 | }
167 | }
168 | });
169 | }
170 | return false;
171 | });
172 | // Focus on the first non-empty text input or password field
173 | function setComboFormFocus() {
174 | $('#combo-form input[type="email"], #combo-form input[type="text"]:visible, #combo-form input[type="password"]:visible, #combo-form input[type="submit"]:visible, #combo-form input[type="tel"]:visible, #combo-form button:visible').each(function(index) {
175 | if($(this).val() === "" || $(this).attr('type') === 'submit' || this.tagName.toLowerCase() === 'button') {
176 | $(this).focus();
177 | return false;
178 | }
179 | });
180 | }
181 | $('#combo-form input[type="radio"]').live('click', function() {
182 | var radioInput = $(this);
183 | if('new' === radioInput.val()) {
184 | $('#combo-form').data('state', 'user_sign_up');
185 | $('#user_forgot_password_fields').slideUp();
186 | $('#user_sign_in_fields').slideUp();
187 | $('#user_sign_up_fields').slideDown(function() {
188 | setComboFormFocus();
189 | });
190 | } else if('existing' === radioInput.val()) {
191 | $('#user_sign_up_fields').slideUp();
192 | $('#user_sign_in_fields').slideDown(function() {
193 | $('#combo-form').data('state', 'user_sign_in');
194 | setComboFormFocus();
195 | $('#user_forgot_password_link').click(function() {
196 | $('#combo-form').data('state', 'user_forgot_password');
197 | $('#user_sign_in_fields').slideUp();
198 | $('#user_forgot_password_fields').slideDown(function() {
199 | setComboFormFocus();
200 | $('#user_remembered_password_link').click(function() {
201 | $('#combo-form').data('state', 'user_sign_in');
202 | $('#user_forgot_password_fields').slideUp();
203 | $('#user_sign_in_fields').slideDown(function() {
204 | setComboFormFocus();
205 | });
206 | });
207 | });
208 | });
209 | });
210 | }
211 | });
212 | $('#combo-form').live('submit', function() {
213 | var submitButton = $("#combo-form input[type='submit']");
214 | $(submitButton).attr("disabled", true);
215 | var errors = []
216 | if(!/[\w\.%\+]+@[\w]+\.+[\w]{2,}/.test($('#user_email').val())) {
217 | errors.push($('#user_email'));
218 | $('#user_email').parent().addClass('error');
219 | } else {
220 | $('#user_email').parent().removeClass('error');
221 | }
222 | if(!$(this).data('state') || $(this).data('state') === 'user_sign_up') {
223 | if($('#user_name').val() === '') {
224 | errors.push($('#user_name'));
225 | $('#user_name').parent().addClass('error');
226 | } else {
227 | $('#user_name').parent().removeClass('error');
228 | }
229 | if($('#user_password_confirmation').val().length < 6 || $('#user_password_confirmation').val().length > 20) {
230 | errors.push($('#user_password_confirmation'));
231 | $('#user_password_confirmation').parent().addClass('error');
232 | } else {
233 | $('#user_password_confirmation').parent().removeClass('error');
234 | }
235 | if(errors.length > 0) {
236 | $(submitButton).attr("disabled", false);
237 | errors[0].focus();
238 | } else {
239 | $.ajax({
240 | type: 'POST',
241 | url: '/users.json',
242 | data: {
243 | 'utf8': '✓',
244 | 'authenticity_token': $('#combo-form input[name="authenticity_token"]').val(),
245 | 'user': {
246 | 'email': $('#user_email').val(),
247 | 'name': $('#user_name').val(),
248 | 'organization': $('#user_organization').val(),
249 | 'voice_number': $('#user_voice_number').val(),
250 | 'sms_number': $('#user_sms_number').val(),
251 | 'password': $('#user_password_confirmation').val(),
252 | 'password_confirmation': $('#user_password_confirmation').val()
253 | }
254 | },
255 | error: function(jqXHR) {
256 | var data = $.parseJSON(jqXHR.responseText);
257 | $(submitButton).attr("disabled", false);
258 | if(data.errors.email) {
259 | errors.push($('#user_email'));
260 | $('#user_email').parent().addClass('error');
261 | }
262 | if(data.errors.name) {
263 | errors.push($('#user_name'));
264 | $('#user_name').parent().addClass('error');
265 | }
266 | if(data.errors.organization) {
267 | errors.push($('#user_organization'));
268 | $('#user_organization').parent().addClass('error');
269 | }
270 | if(data.errors.voice_number) {
271 | errors.push($('#user_voice_number'));
272 | $('#user_voice_number').parent().addClass('error');
273 | }
274 | if(data.errors.sms_number) {
275 | errors.push($('#user_sms_number'));
276 | $('#user_sms_number').parent().addClass('error');
277 | }
278 | if(data.errors.password) {
279 | errors.push($('#user_password_confirmation'));
280 | $('#user_password_confirmation').parent().addClass('error');
281 | }
282 | errors[0].focus();
283 | },
284 | success: function(data) {
285 | $.ajax({
286 | type: 'GET',
287 | url: '/sidebar/search',
288 | data: {
289 | 'flash': {
290 | 'notice': "<%= I18n.t("notices.signed_up") %>"
291 | }
292 | },
293 | success: function(data) {
294 | $('#content').html(data);
295 | }
296 | });
297 | }
298 | });
299 | }
300 | } else if($(this).data('state') === 'user_sign_in') {
301 | if($('#user_password').val().length < 6 || $('#user_password').val().length > 20) {
302 | errors.push($('#user_password'));
303 | $('#user_password').parent().addClass('error');
304 | } else {
305 | $('#user_password').parent().removeClass('error');
306 | }
307 | if(errors.length > 0) {
308 | $(submitButton).attr("disabled", false);
309 | errors[0].focus();
310 | } else {
311 | $.ajax({
312 | type: 'POST',
313 | url: '/users/sign_in.json',
314 | data: {
315 | 'utf8': '✓',
316 | 'authenticity_token': $('#combo-form input[name="authenticity_token"]').val(),
317 | 'user': {
318 | 'email': $('#user_email').val(),
319 | 'password': $('#user_password').val(),
320 | 'remember_me': $('#user_remember_me').val()
321 | }
322 | },
323 | error: function(jqXHR) {
324 | $(submitButton).attr("disabled", false);
325 | $('#user_password').parent().addClass('error');
326 | $('#user_password').focus();
327 | },
328 | success: function(data) {
329 | $.ajax({
330 | type: 'GET',
331 | url: '/sidebar/search',
332 | data: {
333 | 'flash': {
334 | 'notice': "<%= I18n.t("notices.signed_in") %>"
335 | }
336 | },
337 | success: function(data) {
338 | $('#content').html(data);
339 | }
340 | });
341 | }
342 | });
343 | }
344 | } else if($(this).data('state') === 'user_forgot_password') {
345 | if(errors.length > 0) {
346 | $(submitButton).attr("disabled", false);
347 | errors[0].focus();
348 | } else {
349 | $.ajax({
350 | type: 'POST',
351 | url: '/users/password.json',
352 | data: {
353 | 'utf8': '✓',
354 | 'authenticity_token': $('#combo-form input[name="authenticity_token"]').val(),
355 | 'user': {
356 | 'email': $('#user_email').val()
357 | }
358 | },
359 | error: function(jqXHR) {
360 | $(submitButton).attr("disabled", false);
361 | $('#user_email').parent().addClass('error');
362 | $('#user_email').focus();
363 | },
364 | success: function() {
365 | $(submitButton).attr("disabled", false);
366 | $('#user_remembered_password_link').click();
367 | $('#user_password').focus();
368 | }
369 | });
370 | }
371 | }
372 | return false;
373 | });
374 | $('#adoption_form').live('submit', function() {
375 | var submitButton = $("#adoption_form input[type='submit']");
376 | $(submitButton).attr("disabled", true);
377 | $.ajax({
378 | type: 'POST',
379 | url: '/things.json',
380 | data: {
381 | '_method': 'patch',
382 | 'id': $('#thing_id').val(),
383 | 'utf8': '✓',
384 | 'authenticity_token': $('#adoption_form input[name="authenticity_token"]').val(),
385 | 'thing': {
386 | 'user_id': $('#thing_user_id').val(),
387 | 'name': $('#thing_name').val()
388 | }
389 | },
390 | error: function(jqXHR) {
391 | $(submitButton).attr("disabled", false);
392 | },
393 | success: function(data) {
394 | $.ajax({
395 | type: 'GET',
396 | url: '/info_window',
397 | data: {
398 | 'thing_id': activeThingId,
399 | 'flash': {
400 | 'notice': "<%= I18n.t("notices.adopted", thing: I18n.t("defaults.thing")) %>"
401 | }
402 | },
403 | success: function(data) {
404 | activeInfoWindow.close();
405 | activeInfoWindow.setContent(data);
406 | activeInfoWindow.open(map, activeMarker);
407 | activeMarker.setIcon(greenMarkerImage);
408 | activeMarker.setAnimation(google.maps.Animation.BOUNCE);
409 | }
410 | });
411 | }
412 | });
413 | return false;
414 | });
415 | $('#abandon_form').live('submit', function() {
416 | var answer = window.confirm("Are you sure you want to abandon this <%= I18n.t("defaults.thing") %>?")
417 | if(answer) {
418 | var submitButton = $("#abandon_form input[type='submit']");
419 | $(submitButton).attr("disabled", true);
420 | $.ajax({
421 | type: 'POST',
422 | url: '/things.json',
423 | data: {
424 | '_method': 'patch',
425 | 'id': $('#thing_id').val(),
426 | 'utf8': '✓',
427 | 'authenticity_token': $('#abandon_form input[name="authenticity_token"]').val(),
428 | 'thing': {
429 | 'user_id': $('#thing_user_id').val(),
430 | 'name': $('#thing_name').val()
431 | }
432 | },
433 | error: function(jqXHR) {
434 | $(submitButton).attr("disabled", false);
435 | },
436 | success: function(data) {
437 | $.ajax({
438 | type: 'GET',
439 | url: '/info_window',
440 | data: {
441 | 'thing_id': activeThingId,
442 | 'flash': {
443 | 'warning': "<%= I18n.t("notices.abandoned", thing: I18n.t("defaults.thing").capitalize) %>"
444 | }
445 | },
446 | success: function(data) {
447 | activeInfoWindow.close();
448 | activeInfoWindow.setContent(data);
449 | activeInfoWindow.open(map, activeMarker);
450 | activeMarker.setIcon(redMarkerImage);
451 | activeMarker.setAnimation(null);
452 | }
453 | });
454 | }
455 | });
456 | }
457 | return false;
458 | });
459 | $('#edit_profile_link').live('click', function() {
460 | var link = $(this);
461 | $(link).addClass('disabled');
462 | $.ajax({
463 | type: 'GET',
464 | url: '/users/edit',
465 | error: function(jqXHR) {
466 | $(link).removeClass('disabled');
467 | },
468 | success: function(data) {
469 | $('#content').html(data);
470 | }
471 | });
472 | return false;
473 | });
474 | $('#edit_form').live('submit', function() {
475 | var submitButton = $("#edit_form input[type='submit']");
476 | $(submitButton).attr("disabled", true);
477 | var errors = []
478 | if(!/[\w\.%\+\]+@[\w\]+\.+[\w]{2,}/.test($('#user_email').val())) {
479 | errors.push($('#user_email'));
480 | $('#user_email').parent().addClass('error');
481 | } else {
482 | $('#user_email').parent().removeClass('error');
483 | }
484 | if($('#user_name').val() === '') {
485 | errors.push($('#user_name'));
486 | $('#user_name').parent().addClass('error');
487 | } else {
488 | $('#user_name').parent().removeClass('error');
489 | }
490 | if($('#user_zip').val() != '' && !/^\d{5}(-\d{4})?$/.test($('#user_zip').val())) {
491 | errors.push($('#user_zip'));
492 | $('#user_zip').parent().addClass('error');
493 | } else {
494 | $('#user_zip').parent().removeClass('error');
495 | }
496 | if($('#user_password').val() && ($('#user_password').val().length < 6 || $('#user_password').val().length > 20)) {
497 | errors.push($('#user_password'));
498 | $('#user_password').parent().addClass('error');
499 | } else {
500 | $('#user_password').parent().removeClass('error');
501 | }
502 | if($('#user_current_password').val().length < 6 || $('#user_current_password').val().length > 20) {
503 | errors.push($('#user_current_password'));
504 | $('#user_current_password').parent().addClass('error');
505 | } else {
506 | $('#user_current_password').parent().removeClass('error');
507 | }
508 | if(errors.length > 0) {
509 | $(submitButton).attr("disabled", false);
510 | errors[0].focus();
511 | } else {
512 | $.ajax({
513 | type: 'POST',
514 | url: '/users.json',
515 | data: {
516 | '_method': 'patch',
517 | 'id': $('#id').val(),
518 | 'thing_id': activeThingId,
519 | 'utf8': '✓',
520 | 'authenticity_token': $('#edit_form input[name="authenticity_token"]').val(),
521 | 'user': {
522 | 'email': $('#user_email').val(),
523 | 'name': $('#user_name').val(),
524 | 'organization': $('#user_organization').val(),
525 | 'voice_number': $('#user_voice_number').val(),
526 | 'sms_number': $('#user_sms_number').val(),
527 | 'address_1': $('#user_address_1').val(),
528 | 'address_2': $('#user_address_2').val(),
529 | 'city': $('#user_city').val(),
530 | 'state': $('#user_state').val(),
531 | 'zip': $('#user_zip').val(),
532 | 'password': $('#user_password').val(),
533 | 'password_confirmation': $('#user_password').val(),
534 | 'current_password': $('#user_current_password').val()
535 | }
536 | },
537 | error: function(jqXHR) {
538 | var data = $.parseJSON(jqXHR.responseText);
539 | $(submitButton).attr("disabled", false);
540 | if(data.errors.email) {
541 | errors.push($('#user_email'));
542 | $('#user_email').parent().addClass('error');
543 | }
544 | if(data.errors.name) {
545 | errors.push($('#user_name'));
546 | $('#user_name').parent().addClass('error');
547 | }
548 | if(data.errors.organization) {
549 | errors.push($('#user_organization'));
550 | $('#user_organization').parent().addClass('error');
551 | }
552 | if(data.errors.voice_number) {
553 | errors.push($('#user_voice_number'));
554 | $('#user_voice_number').parent().addClass('error');
555 | }
556 | if(data.errors.sms_number) {
557 | errors.push($('#user_sms_number'));
558 | $('#user_sms_number').parent().addClass('error');
559 | }
560 | if(data.errors.address_1) {
561 | errors.push($('#user_address_1'));
562 | $('#user_address_1').parent().addClass('error');
563 | }
564 | if(data.errors.address_2) {
565 | errors.push($('#user_address_2'));
566 | $('#user_address_2').parent().addClass('error');
567 | }
568 | if(data.errors.city) {
569 | errors.push($('#user_city'));
570 | $('#user_city').parent().addClass('error');
571 | }
572 | if(data.errors.state) {
573 | errors.push($('#user_state'));
574 | $('#user_state').parent().addClass('error');
575 | }
576 | if(data.errors.zip) {
577 | errors.push($('#user_zip'));
578 | $('#user_zip').parent().addClass('error');
579 | }
580 | if(data.errors.password) {
581 | errors.push($('#user_password'));
582 | $('#user_password').parent().addClass('error');
583 | }
584 | if(data.errors.current_password) {
585 | errors.push($('#user_current_password'));
586 | $('#user_current_password').parent().addClass('error');
587 | }
588 | errors[0].focus();
589 | },
590 | success: function(data) {
591 | $('#content').html(data);
592 | }
593 | });
594 | }
595 | return false;
596 | });
597 | $('#sign_out_link').live('click', function() {
598 | var link = $(this);
599 | $(link).addClass('disabled');
600 | $.ajax({
601 | type: 'DELETE',
602 | url: '/users/sign_out.json',
603 | error: function(jqXHR) {
604 | $(link).removeClass('disabled');
605 | },
606 | success: function(data) {
607 | $.ajax({
608 | type: 'GET',
609 | url: '/sidebar/combo_form',
610 | data: {
611 | 'flash': {
612 | 'warning': "<%= I18n.t("notices.signed_out") %>"
613 | }
614 | },
615 | success: function(data) {
616 | $('#content').html(data);
617 | }
618 | });
619 | }
620 | });
621 | return false;
622 | });
623 | $('#sign_in_form').live('submit', function() {
624 | var submitButton = $("#sign_in_form input[type='submit']");
625 | $(submitButton).attr("disabled", true);
626 | $.ajax({
627 | type: 'GET',
628 | url: '/users/sign_in',
629 | error: function(jqXHR) {
630 | $(submitButton).attr("disabled", false);
631 | },
632 | success: function(data) {
633 | activeInfoWindow.close();
634 | activeInfoWindow.setContent(data);
635 | activeInfoWindow.open(map, activeMarker);
636 | }
637 | });
638 | return false;
639 | });
640 | $('#back_link').live('click', function() {
641 | var link = $(this);
642 | $(link).addClass('disabled');
643 | $.ajax({
644 | type: 'GET',
645 | url: '/sidebar/search',
646 | error: function(jqXHR) {
647 | $(link).removeClass('disabled');
648 | },
649 | success: function(data) {
650 | $('#content').html(data);
651 | }
652 | });
653 | return false;
654 | });
655 | $('#reminder_form').live('submit', function() {
656 | var submitButton = $("#reminder_form input[type='submit']");
657 | $(submitButton).attr("disabled", true);
658 | $.ajax({
659 | type: 'POST',
660 | url: '/reminders.json',
661 | data: {
662 | 'utf8': '✓',
663 | 'authenticity_token': $('#reminder_form input[name="authenticity_token"]').val(),
664 | 'reminder': {
665 | 'to_user_id': $('#reminder_to_user_id').val(),
666 | 'thing_id': activeThingId
667 | }
668 | },
669 | error: function(jqXHR) {
670 | $(submitButton).attr("disabled", false);
671 | },
672 | success: function(data) {
673 | $.ajax({
674 | type: 'GET',
675 | url: '/info_window',
676 | data: {
677 | 'thing_id': activeThingId,
678 | 'flash': {
679 | 'notice': "<%= I18n.t("notices.reminder_sent") %>"
680 | }
681 | },
682 | success: function(data) {
683 | activeInfoWindow.close();
684 | activeInfoWindow.setContent(data);
685 | activeInfoWindow.open(map, activeMarker);
686 | }
687 | });
688 | }
689 | });
690 | return false;
691 | });
692 | $('.alert-message').alert();
693 | });
694 |
--------------------------------------------------------------------------------