├── app ├── mailers │ ├── .keep │ ├── custom_devise_mailer.rb │ └── mailer.rb ├── models │ ├── .keep │ ├── concerns │ │ └── .keep │ ├── category.rb │ ├── department.rb │ ├── audit.rb │ ├── saved_search.rb │ ├── attachment.rb │ └── question.rb ├── assets │ ├── images │ │ ├── .keep │ │ └── hero.jpg │ ├── javascripts │ │ ├── sections │ │ │ ├── .keep │ │ │ └── opportunities │ │ │ │ ├── edit.coffee │ │ │ │ └── index.coffee │ │ ├── components │ │ │ ├── .keep │ │ │ ├── selectize.coffee │ │ │ ├── data_toggle_visible.coffee │ │ │ ├── data_show_if_checked.coffee │ │ │ ├── data_toggle_text.coffee │ │ │ ├── data_show_if_selected.coffee │ │ │ ├── submission_adapters.coffee │ │ │ ├── datetime_picker.coffee │ │ │ ├── progress_guard.coffee │ │ │ └── attachment_upload.coffee │ │ ├── theme.js │ │ ├── base.coffee │ │ └── application.js │ └── stylesheets │ │ ├── theme.css │ │ └── application.css ├── controllers │ ├── concerns │ │ └── .keep │ ├── admin │ │ ├── users_controller.rb │ │ ├── questions_controller.rb │ │ ├── attachments_controller.rb │ │ ├── categories_controller.rb │ │ ├── departments_controller.rb │ │ ├── opportunities_controller.rb │ │ ├── saved_searches_controller.rb │ │ └── application_controller.rb │ ├── static_controller.rb │ ├── users │ │ ├── passwords_controller.rb │ │ ├── sessions_controller.rb │ │ ├── confirmations_controller.rb │ │ └── registrations_controller.rb │ ├── home_controller.rb │ ├── saved_searches_controller.rb │ ├── attachments_controller.rb │ ├── application_controller.rb │ └── questions_controller.rb ├── jobs │ ├── application_job.rb │ ├── queue_user_search_results_job.rb │ ├── send_deadline_reminders_job.rb │ └── user_search_results_job.rb ├── views │ ├── users │ │ ├── registrations │ │ │ ├── edit.html.erb │ │ │ ├── new.html.erb │ │ │ ├── _business_data.html.erb │ │ │ ├── confirm.erb │ │ │ ├── _my_opportunities.erb │ │ │ ├── _edit_password.html.erb │ │ │ ├── _saved_searches.html.erb │ │ │ ├── _new_staff.erb │ │ │ ├── _new_vendor.html.erb │ │ │ └── _edit_account.html.erb │ │ ├── passwords │ │ │ ├── new.html.erb │ │ │ └── edit.html.erb │ │ ├── confirmations │ │ │ └── new.html.erb │ │ └── sessions │ │ │ └── new.html.erb │ ├── mailer │ │ ├── _greeting.html.erb │ │ ├── search_results.html.erb │ │ ├── question_deadline.html.erb │ │ ├── submission_deadline.html.erb │ │ ├── approval_request.html.erb │ │ ├── question_answered.html.erb │ │ └── question_asked.html.erb │ ├── application │ │ ├── _flashes.html.erb │ │ ├── _inner_layout.html.erb │ │ ├── _webfonts.html.erb │ │ └── _footer.html.erb │ ├── custom_devise_mailer │ │ ├── password_change.html.erb │ │ ├── confirmation_instructions.html.erb │ │ └── reset_password_instructions.html.erb │ ├── opportunities │ │ ├── edit │ │ │ ├── _contact.html.erb │ │ │ ├── _submission_adapter_inputs.html.erb │ │ │ ├── _submissions.erb │ │ │ └── _description.html.erb │ │ ├── actions │ │ │ ├── _edit.erb │ │ │ ├── _unapprove.erb │ │ │ ├── _destroy.erb │ │ │ ├── _submission_instructions.erb │ │ │ ├── _answer_questions.html.erb │ │ │ ├── _review_submissions.html.erb │ │ │ ├── _ask_question.html.erb │ │ │ └── _submit.html.erb │ │ ├── _description.html.erb │ │ ├── _edit_attachment.erb │ │ ├── _admin_links.erb │ │ ├── submit.html.erb │ │ ├── _vendor_actions.html.erb │ │ ├── _timeline.html.erb │ │ ├── _attachments.html.erb │ │ ├── _pending_item.html.erb │ │ ├── new.html.erb │ │ ├── feed.xml.builder │ │ ├── _subscribe_button.html.erb │ │ ├── _view_attachment.html.erb │ │ ├── _contact_info.html.erb │ │ ├── show.html.erb │ │ ├── index.html.erb │ │ ├── pending.html.erb │ │ ├── _questions.erb │ │ ├── _admin_status.html.erb │ │ ├── _search_results.html.erb │ │ ├── edit.html.erb │ │ └── _filter_form.html.erb │ ├── submission_adapters │ │ ├── email │ │ │ └── _edit.html.erb │ │ └── screendoor │ │ │ ├── _edit.html.erb │ │ │ └── _submit.html.erb │ ├── home │ │ ├── index.html.erb │ │ ├── _hero.html.erb │ │ ├── _about.html.erb │ │ └── _recent_opportunities.html.erb │ ├── static │ │ └── about.html.erb │ ├── layouts │ │ ├── application.html.erb │ │ └── base_mailer.html.erb │ └── questions │ │ └── _question.html.erb ├── helpers │ ├── build_email_address_helper.rb │ ├── pick_helper.rb │ ├── formatting_helper.rb │ ├── formatted_timestamp_helper.rb │ ├── opportunities_helper.rb │ └── application_helper.rb ├── inputs │ └── datetime_picker_input.rb ├── uploaders │ ├── base_uploader.rb │ └── attachment_uploader.rb ├── filterers │ └── opportunity_filterer.rb ├── policies │ └── opportunity_policy.rb └── dashboards │ ├── saved_search_dashboard.rb │ ├── category_dashboard.rb │ ├── department_dashboard.rb │ ├── attachment_dashboard.rb │ └── question_dashboard.rb ├── lib ├── tasks │ ├── .keep │ ├── db.rake │ └── auto_annotate_models.rake ├── submission_adapters │ ├── none.rb │ ├── screendoor.rb │ ├── email.rb │ └── base.rb ├── submission_adapters.rb └── dispatch_configuration.rb ├── spec ├── models │ ├── .keep │ ├── category_spec.rb │ ├── department_spec.rb │ ├── saved_search_spec.rb │ ├── question_spec.rb │ ├── attachment_spec.rb │ └── user_spec.rb ├── factories │ ├── .keep │ ├── saved_searches.rb │ ├── departments.rb │ ├── categories.rb │ ├── attachments.rb │ ├── questions.rb │ └── users.rb ├── fixtures │ └── files │ │ ├── empty │ │ ├── test.txt │ │ └── test.docx ├── support │ ├── webmock.rb │ ├── database_cleaner.rb │ └── utilities.rb ├── lib │ └── dispatch_configuration_spec.rb ├── i18n_spec.rb ├── mailers │ └── previews │ │ ├── custom_devise_mailer_preview.rb │ │ └── mailer_preview.rb ├── features │ ├── sign_in_spec.rb │ ├── home_spec.rb │ ├── opportunities │ │ ├── pending_spec.rb │ │ ├── index_spec.rb │ │ └── create_spec.rb │ └── admin │ │ └── base_spec.rb ├── spec_helper.rb └── jobs │ ├── user_search_results_job_spec.rb │ └── send_deadline_reminders_job_spec.rb ├── .ruby-version ├── themes └── dvl-core │ ├── views │ ├── .keep │ └── application │ │ └── _flashes.html.erb │ ├── assets │ ├── stylesheets │ │ ├── theme │ │ │ ├── layout │ │ │ │ ├── .keep │ │ │ │ └── application.scss │ │ │ ├── components │ │ │ │ ├── .keep │ │ │ │ ├── signin_link.scss │ │ │ │ ├── admin_actions.scss │ │ │ │ ├── password_shim.scss │ │ │ │ ├── simple_alert.scss │ │ │ │ ├── opportunities_table.scss │ │ │ │ ├── date_range.scss │ │ │ │ ├── search_result_count.scss │ │ │ │ ├── opportunity_actions.scss │ │ │ │ ├── opportunity.scss │ │ │ │ ├── filter_form.scss │ │ │ │ ├── info_box.scss │ │ │ │ ├── opportunity_timeline.scss │ │ │ │ └── account_saved_searches.scss │ │ │ ├── includes.scss │ │ │ ├── overrides │ │ │ │ ├── sidebar_box.scss │ │ │ │ ├── navbar.scss │ │ │ │ ├── hero.scss │ │ │ │ ├── page_header.scss │ │ │ │ └── rome.scss │ │ │ ├── sections │ │ │ │ ├── opportunities │ │ │ │ │ ├── pending.scss │ │ │ │ │ ├── show.scss │ │ │ │ │ └── edit.scss │ │ │ │ └── index.scss │ │ │ ├── typography_overrides.scss │ │ │ ├── branding.scss │ │ │ └── dvl_core.scss │ │ └── theme.css │ └── javascripts │ │ ├── initialize.coffee │ │ ├── theme.js │ │ └── selectize_monkeypatch.coffee │ ├── locales │ └── en.yml │ ├── simple_form.rb │ └── submission_adapters │ └── dvl.rb ├── .tool-versions ├── vendor └── assets │ ├── javascripts │ └── .keep │ └── stylesheets │ └── .keep ├── public ├── robots.txt ├── favicon.ico ├── apple-touch-icon-precomposed.png ├── non_digest_assets │ └── README.txt ├── humans.txt ├── 500.html ├── 422.html └── 404.html ├── Procfile ├── docs ├── screenshot.png ├── developing_dispatch_core.md ├── setting_up_a_development_environment.md ├── marketing.md └── deployment.md ├── CHANGELOG.md ├── script ├── bootstrap ├── cibuild └── server ├── config ├── environments │ ├── staging.rb │ ├── production.rb │ ├── _smtp_env_vars.rb │ ├── development.rb │ ├── _shared.rb │ ├── test.rb │ └── _shared_staging_production.rb ├── initializers │ ├── add_flash_types.rb │ ├── filter_parameter_logging.rb │ ├── mime_types.rb │ ├── session_store.rb │ ├── secret_token.rb │ ├── wrap_parameters.rb │ ├── carrierwave.rb │ ├── inflections.rb │ ├── theme.rb │ └── whitelist_interceptor.rb ├── database.ci.yml ├── environment.rb ├── boot.rb ├── database.yml ├── schedule.rb ├── locales │ └── simple_form.en.yml ├── application.rb └── routes.rb ├── bin ├── bundle ├── rake ├── rspec ├── rails ├── guard └── spring ├── config.ru ├── db ├── migrate │ ├── 20160425151213_add_text_content_to_attachments.rb │ └── 20160418203004_create_delayed_jobs.rb └── seeds.rb ├── .editorconfig ├── Rakefile ├── circle.yml ├── .gitignore ├── Guardfile ├── .rubocop.yml ├── Gemfile └── config.yml.example /app/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.3.1 2 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/factories/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/files/empty: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/dvl-core/views/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | ruby 2.3.1 2 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/javascripts/sections/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/files/test.txt: -------------------------------------------------------------------------------- 1 | hi! 2 | -------------------------------------------------------------------------------- /app/assets/javascripts/components/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/layout/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/dvl-core/locales/en.yml: -------------------------------------------------------------------------------- 1 | --- 2 | en: 3 | # 4 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/components/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/dvl-core/simple_form.rb: -------------------------------------------------------------------------------- 1 | require 'dvl/simple_form_config' 2 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/views/users/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= render "edit_#{@edit_type}" %> 2 | -------------------------------------------------------------------------------- /app/views/users/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= render "new_#{@signup_type}" %> 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec thin start -p $PORT 2 | worker: bundle exec rake jobs:work 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dobtco/dispatch/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /app/assets/javascripts/theme.js: -------------------------------------------------------------------------------- 1 | /* This will be overridden in the ./theme directory */ 2 | -------------------------------------------------------------------------------- /app/assets/stylesheets/theme.css: -------------------------------------------------------------------------------- 1 | /* This will be overridden in the ./theme directory */ 2 | -------------------------------------------------------------------------------- /app/views/mailer/_greeting.html.erb: -------------------------------------------------------------------------------- 1 |

<%= t('mailer.greeting', name: @user.name) %>

2 | -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dobtco/dispatch/HEAD/docs/screenshot.png -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change log 2 | --- 3 | 4 | (Will be populated once Dispatch is slightly more stable.) 5 | -------------------------------------------------------------------------------- /app/assets/images/hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dobtco/dispatch/HEAD/app/assets/images/hero.jpg -------------------------------------------------------------------------------- /app/assets/javascripts/components/selectize.coffee: -------------------------------------------------------------------------------- 1 | $ -> 2 | $('select[multiple]').selectize() 3 | -------------------------------------------------------------------------------- /script/bootstrap: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | bundle install 4 | rake db:setup 5 | rake db:seed:example 6 | -------------------------------------------------------------------------------- /config/environments/staging.rb: -------------------------------------------------------------------------------- 1 | require_relative '_shared_staging_production' 2 | require_relative '_shared' 3 | -------------------------------------------------------------------------------- /config/initializers/add_flash_types.rb: -------------------------------------------------------------------------------- 1 | ActionController::Base.send :add_flash_types, :error, :info, :success 2 | -------------------------------------------------------------------------------- /spec/fixtures/files/test.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dobtco/dispatch/HEAD/spec/fixtures/files/test.docx -------------------------------------------------------------------------------- /app/mailers/custom_devise_mailer.rb: -------------------------------------------------------------------------------- 1 | class CustomDeviseMailer < Devise::Mailer 2 | layout 'base_mailer' 3 | end 4 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | require_relative '_shared_staging_production' 2 | require_relative '_shared' 3 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dobtco/dispatch/HEAD/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /app/controllers/admin/users_controller.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class UsersController < Admin::ApplicationController 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/static_controller.rb: -------------------------------------------------------------------------------- 1 | class StaticController < ApplicationController 2 | before_action :skip_authorization 3 | end 4 | -------------------------------------------------------------------------------- /config/database.ci.yml: -------------------------------------------------------------------------------- 1 | test: 2 | host: localhost 3 | username: ubuntu 4 | database: circle_ruby_test 5 | adapter: postgresql 6 | -------------------------------------------------------------------------------- /app/controllers/admin/questions_controller.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class QuestionsController < Admin::ApplicationController 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/users/passwords_controller.rb: -------------------------------------------------------------------------------- 1 | module Users 2 | class PasswordsController < Devise::PasswordsController 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/users/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | module Users 2 | class SessionsController < Devise::SessionsController 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/admin/attachments_controller.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class AttachmentsController < Admin::ApplicationController 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/admin/categories_controller.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class CategoriesController < Admin::ApplicationController 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/admin/departments_controller.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class DepartmentsController < Admin::ApplicationController 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /script/cibuild: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | set -e 4 | export RAILS_ENV=test 5 | bundle exec brakeman -z 6 | bundle exec rspec 7 | bundle exec rubocop 8 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/includes.scss: -------------------------------------------------------------------------------- 1 | @import 'dvl/core/includes'; 2 | @import 'branding'; 3 | @import 'typography_overrides'; 4 | -------------------------------------------------------------------------------- /app/controllers/admin/opportunities_controller.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class OpportunitiesController < Admin::ApplicationController 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/admin/saved_searches_controller.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class SavedSearchesController < Admin::ApplicationController 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /public/non_digest_assets/README.txt: -------------------------------------------------------------------------------- 1 | Wondering why this is here? Have fun: https://github.com/rails/sprockets-rails/issues/49#issuecomment-20535134 2 | -------------------------------------------------------------------------------- /app/views/application/_flashes.html.erb: -------------------------------------------------------------------------------- 1 | <% flashes_with_consistent_keys.each do |k, v| %> 2 |

<%= v %>

3 | <% end %> 4 | -------------------------------------------------------------------------------- /app/views/users/registrations/_business_data.html.erb: -------------------------------------------------------------------------------- 1 | <% # f.input :"8a", as: :boolean, inline_label: 'Are you registered as an 8(a)?', label: false %> 2 | -------------------------------------------------------------------------------- /app/assets/javascripts/components/data_toggle_visible.coffee: -------------------------------------------------------------------------------- 1 | $(document).on 'click', '[data-toggle-visible]', -> 2 | $($(@).data('toggle-visible')).toggle() 3 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/javascripts/initialize.coffee: -------------------------------------------------------------------------------- 1 | $ -> 2 | $('body').styledSelect() 3 | $('body').styledControls() 4 | $('body').formattedTimestamps() 5 | -------------------------------------------------------------------------------- /lib/submission_adapters/none.rb: -------------------------------------------------------------------------------- 1 | module SubmissionAdapters 2 | class None < SubmissionAdapters::Base 3 | self.select_text = 'None of the above' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/overrides/sidebar_box.scss: -------------------------------------------------------------------------------- 1 | @import 'theme/includes'; 2 | 3 | .sidebar_box + .sidebar_box { 4 | margin-top: $rhythm * 2; 5 | } 6 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /public/humans.txt: -------------------------------------------------------------------------------- 1 | # TEAM 2 | Department of Better Technology 3 | We design and build awesome software for government. 4 | http://www.dobt.co 5 | hello@dobt.co 6 | @dobtco 7 | -------------------------------------------------------------------------------- /spec/support/webmock.rb: -------------------------------------------------------------------------------- 1 | require 'webmock/rspec' 2 | 3 | WebMock.disable_net_connect!( 4 | allow_localhost: true, 5 | allow: ['www.example.com', 'codeclimate.com'] 6 | ) 7 | -------------------------------------------------------------------------------- /app/views/custom_devise_mailer/password_change.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.name %>!

2 | 3 |

We're contacting you to notify you that your password has been changed.

4 | -------------------------------------------------------------------------------- /app/assets/javascripts/sections/opportunities/edit.coffee: -------------------------------------------------------------------------------- 1 | $ -> 2 | return unless $('body').data('page-key') == 'opportunities-edit' 3 | $('form.edit_opportunity').progressGuard() 4 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/components/signin_link.scss: -------------------------------------------------------------------------------- 1 | @import 'theme/includes'; 2 | 3 | .sign_in_link { 4 | margin-top: $lineHeight; 5 | font-size: $fontSmaller; 6 | } 7 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/sections/opportunities/pending.scss: -------------------------------------------------------------------------------- 1 | @import 'theme/includes'; 2 | 3 | .page_header_pending_opportunities { 4 | padding-top: $rhythm * 2; 5 | } 6 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/sections/opportunities/show.scss: -------------------------------------------------------------------------------- 1 | @import 'theme/includes'; 2 | 3 | .submission_instructions { 4 | margin-bottom: $rhythm; // consistency 5 | } 6 | -------------------------------------------------------------------------------- /app/views/application/_inner_layout.html.erb: -------------------------------------------------------------------------------- 1 | <%= yield(:before_container) %> 2 | 3 |
4 | <%= yield %> 5 |
6 | -------------------------------------------------------------------------------- /app/assets/javascripts/sections/opportunities/index.coffee: -------------------------------------------------------------------------------- 1 | $(document).on 'click', '.js-toggle-filters', -> 2 | $(@).toggleClass('is_active') 3 | $('.info_box_filters').toggleClass('is_active') 4 | -------------------------------------------------------------------------------- /app/views/opportunities/edit/_contact.html.erb: -------------------------------------------------------------------------------- 1 | <%= f.input :contact_name, label: t('name') %> 2 | <%= f.input :contact_email, label: t('email') %> 3 | <%= f.input :contact_phone, label: t('phone') %> 4 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/components/admin_actions.scss: -------------------------------------------------------------------------------- 1 | @import 'theme/includes'; 2 | 3 | .admin_actions { 4 | margin-top: $rhythm; 5 | li { 6 | text-align: right; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /app/views/opportunities/actions/_edit.erb: -------------------------------------------------------------------------------- 1 | <% if policy(@opportunity).edit? && @opportunity.approved? %> 2 |
  • 3 | <%= t('edit') %> 4 |
  • 5 | <% end %> 6 | -------------------------------------------------------------------------------- /db/migrate/20160425151213_add_text_content_to_attachments.rb: -------------------------------------------------------------------------------- 1 | class AddTextContentToAttachments < ActiveRecord::Migration 2 | def change 3 | add_column :attachments, :text_content, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /script/server: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | cd $(dirname "$0")/.. 6 | : ${RAILS_ENV:=development} 7 | : ${RACK_ENV:=development} 8 | 9 | export RAILS_ENV RACK_ENV 10 | 11 | bundle exec thin start 12 | -------------------------------------------------------------------------------- /app/jobs/queue_user_search_results_job.rb: -------------------------------------------------------------------------------- 1 | class QueueUserSearchResultsJob < ApplicationJob 2 | def perform 3 | User.find_each do |user| 4 | UserSearchResultsJob.perform_later(user) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/views/submission_adapters/email/_edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= f.input :submit_to do %> 2 | <%= f.input :name, required: true, wrapper: :vertical %> 3 | <%= f.input :email, required: true, wrapper: :vertical %> 4 | <% end %> 5 | -------------------------------------------------------------------------------- /app/views/application/_webfonts.html.erb: -------------------------------------------------------------------------------- 1 | <% unless Rails.env.test? %> 2 | 3 | <% end %> 4 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme.css: -------------------------------------------------------------------------------- 1 | /* 2 | *= require theme/dvl_core 3 | *= require_tree ./theme/layout 4 | *= require_tree ./theme/components 5 | *= require_tree ./theme/overrides 6 | *= require_tree ./theme/sections 7 | */ 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | indent_style = spaces 8 | indent_size = 2 9 | 10 | [*.md] 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /app/views/submission_adapters/screendoor/_edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= f.input :embed_token, required: true do %> 2 | <%= f.input_field :embed_token %> 3 |
    <%= t('embed_token_hint') %>
    4 | <% end %> 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require 'bundler/setup' 8 | load Gem.bin_path('rake', 'rake') 9 | -------------------------------------------------------------------------------- /spec/lib/dispatch_configuration_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DispatchConfiguration do 4 | it 'loads the defaults in config.yml.example' do 5 | expect(described_class.upload_storage).to eq 'file' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/assets/javascripts/base.coffee: -------------------------------------------------------------------------------- 1 | # Allow us to use href='#' without jumping to the top of the page. 2 | # Otherwise, we'd have to use javascript:void(0) which sucks. 3 | $(document).on 'click', '[href="#"]', (e) -> 4 | e.preventDefault() 5 | -------------------------------------------------------------------------------- /app/views/opportunities/actions/_unapprove.erb: -------------------------------------------------------------------------------- 1 | <% if policy(@opportunity).approve? && @opportunity.approved? %> 2 |
  • 3 | <%= t('unapprove') %> 4 |
  • 5 | <% end %> 6 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require 'bundler/setup' 8 | load Gem.bin_path('rspec-core', 'rspec') 9 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/javascripts/theme.js: -------------------------------------------------------------------------------- 1 | //= require_self 2 | //= require dvl/core/styled_select 3 | //= require dvl/core/styled_controls 4 | //= require dvl/components/navbar 5 | //= require dvl/components/flashes 6 | //= require_tree . 7 | 8 | var Dvl = {}; 9 | -------------------------------------------------------------------------------- /themes/dvl-core/submission_adapters/dvl.rb: -------------------------------------------------------------------------------- 1 | # class DvlSubmission < SubmissionAdapters::Base 2 | # self.name = 'Dvl' 3 | # 4 | # def submit_proposals_instructions 5 | # 'I dunno!' 6 | # end 7 | # end 8 | # 9 | # SubmissionAdapters.all_adapters << DvlSubmission 10 | -------------------------------------------------------------------------------- /themes/dvl-core/views/application/_flashes.html.erb: -------------------------------------------------------------------------------- 1 | <% flashes_with_consistent_keys.each do |k, (text, links)| %> 2 | 7 | <% end %> 8 | -------------------------------------------------------------------------------- /app/views/opportunities/actions/_destroy.erb: -------------------------------------------------------------------------------- 1 | <% if policy(@opportunity).destroy? %> 2 |
  • 3 | '> 4 | <%= t('destroy') %> 5 | 6 |
  • 7 | <% end %> 8 | -------------------------------------------------------------------------------- /app/views/users/registrations/confirm.erb: -------------------------------------------------------------------------------- 1 | <% content_for(:page_title, t('.title')) %> 2 | <% content_for(:main_container_class, 'container_tiny') %> 3 | 4 | 7 | 8 |

    <%= t('.body') %>

    9 | -------------------------------------------------------------------------------- /app/views/opportunities/_description.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <% if @opportunity.description.present? %> 3 | <%= format_textarea_input(@opportunity.description) %> 4 | <% else %> 5 |

    <%= t('no_description') %>

    6 | <% end %> 7 |
    8 | -------------------------------------------------------------------------------- /app/assets/javascripts/components/data_show_if_checked.coffee: -------------------------------------------------------------------------------- 1 | $ -> 2 | $('[data-show-if-checked]').each -> 3 | $input = $("input[name=\"#{$(@).data('show-if-checked')}\"][type=checkbox]") 4 | 5 | $input.on 'click', => 6 | if $input.is(':checked') then $(@).show() else $(@).hide() 7 | -------------------------------------------------------------------------------- /lib/submission_adapters.rb: -------------------------------------------------------------------------------- 1 | module SubmissionAdapters 2 | class << self 3 | mattr_accessor :all_adapters do 4 | [ 5 | SubmissionAdapters::Email, 6 | SubmissionAdapters::Screendoor, 7 | SubmissionAdapters::None 8 | ] 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/opportunities/actions/_submission_instructions.erb: -------------------------------------------------------------------------------- 1 | <% if policy(@opportunity).submit? && @opportunity.submission_adapter.submit_proposals_instructions %> 2 |

    <%= @opportunity.submission_adapter.submit_proposals_instructions %>

    3 | <% end %> 4 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, 4 | key: '_dispatch_session', 5 | secure: DispatchConfiguration.ssl 6 | -------------------------------------------------------------------------------- /app/views/opportunities/_edit_attachment.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | <%= attachment.upload.raw_filename %>
    3 | × <%= t('destroy') %> 4 |
  • 5 | -------------------------------------------------------------------------------- /app/assets/javascripts/components/data_toggle_text.coffee: -------------------------------------------------------------------------------- 1 | $.fn.extend 2 | toggleText: -> 3 | @each -> 4 | newText = $(@).data('toggle-text') 5 | $(@).data 'toggle-text', $(@).text() 6 | $(@).text newText 7 | 8 | $(document).on 'click', '[data-toggle-text]', -> 9 | $(@).toggleText() 10 | -------------------------------------------------------------------------------- /app/helpers/build_email_address_helper.rb: -------------------------------------------------------------------------------- 1 | module BuildEmailAddressHelper 2 | def build_email_address(email, name) 3 | address = Mail::Address.new(email) 4 | address.display_name = name.dup if name.present? 5 | address.format 6 | rescue Mail::Field::ParseError 7 | email 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../../config/application', __FILE__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/overrides/navbar.scss: -------------------------------------------------------------------------------- 1 | @import 'theme/includes'; 2 | 3 | .navbar_brand { 4 | font-family: $fontFamilyDisplay; 5 | font-size: 2rem; 6 | } 7 | 8 | @media screen and (min-width: $deskWidth) { 9 | .navbar .button { 10 | margin-left: $rhythm * 2; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/views/opportunities/actions/_answer_questions.html.erb: -------------------------------------------------------------------------------- 1 | <% if policy(@opportunity).answer_questions? && @opportunity.questions.unanswered.count > 0 %> 2 |
  • 3 | 4 | <%= t('unanswered_questions', count: @opportunity.questions.unanswered.count) %> 5 | 6 |
  • 7 | <% end %> 8 | -------------------------------------------------------------------------------- /app/views/mailer/search_results.html.erb: -------------------------------------------------------------------------------- 1 | <%= render('greeting') %> 2 | 3 |

    <%= t('mailer.search_results.body') %>

    4 | 5 | 12 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: postgresql 3 | database: dispatch_development 4 | port: <%= ENV["BOXEN_POSTGRESQL_PORT"] || 5432 %> 5 | host: localhost 6 | 7 | test: 8 | adapter: postgresql 9 | database: dispatch_test 10 | port: <%= ENV["BOXEN_POSTGRESQL_PORT"] || 5432 %> 11 | host: localhost 12 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/components/password_shim.scss: -------------------------------------------------------------------------------- 1 | @import 'theme/includes'; 2 | 3 | .password_shim { 4 | line-height: $rhythm * 4; 5 | letter-spacing: 0.2rem; 6 | position: relative; 7 | top: $rhythm / -2; 8 | font-size: $lineHeight; 9 | color: $darkerGray; 10 | margin-right: $rhythm / 2; 11 | } 12 | -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | def index 3 | skip_authorization 4 | 5 | @recent_opportunities = Opportunity. 6 | posted. 7 | order_by_recently_posted. 8 | limit(5) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/views/home/index.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for(:page_title, t('home.hero.title')) %> 2 | <% content_for(:page_key, 'index') %> 3 | 4 | <% content_for(:before_container) do %> 5 | <%= render('hero') %> 6 | <% end %> 7 | 8 |
    9 | <%= render('recent_opportunities') %> 10 | <%= render('about') %> 11 |
    12 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/components/simple_alert.scss: -------------------------------------------------------------------------------- 1 | @import 'theme/includes'; 2 | 3 | .simple_alert { 4 | background: $lightestGray; 5 | text-align: center; 6 | display: block; 7 | color: $darkestGray; 8 | @include font_smoothing; 9 | padding: $rhythm ($rhythm * 1.5); 10 | border-radius: $radius; 11 | } 12 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/typography_overrides.scss: -------------------------------------------------------------------------------- 1 | $fontLineHeightH1: $rhythm * 8; 2 | $fontLineHeightH2: $rhythm * 5; 3 | 4 | $weightBold: 700; 5 | 6 | h3 { 7 | @include fontDisplay; 8 | } 9 | 10 | @media screen and (max-width: $lapWidth) { 11 | h2, h3 { 12 | font-family: $fontFamilyDisplay; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/views/custom_devise_mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

    Welcome, <%= @resource.name %>!

    2 | 3 |

    To finish signing up, just confirm your email address by pressing the button below:

    4 | 5 |

    Confirm my account

    6 | -------------------------------------------------------------------------------- /app/views/opportunities/_admin_links.erb: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /app/views/opportunities/submit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for(:page_title, t('submit_proposal')) %> 2 | <% content_for(:main_container_class, 'container_small') %> 3 | 4 | 7 | 8 | <%= render "submission_adapters/#{@opportunity.submission_adapter.to_param}/submit" %> 9 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/components/opportunities_table.scss: -------------------------------------------------------------------------------- 1 | @import 'theme/includes'; 2 | 3 | .opportunities_table_item { 4 | tr:first-child { 5 | td { 6 | border-bottom: 0; 7 | padding-bottom: 0; 8 | } 9 | } 10 | 11 | tr:last-child { 12 | font-size: $fontSmall; 13 | color: $darkerGray; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/controllers/users/confirmations_controller.rb: -------------------------------------------------------------------------------- 1 | module Users 2 | class ConfirmationsController < Devise::ConfirmationsController 3 | # Automatically sign in after confirming email address. 4 | def show 5 | super do 6 | if resource.errors.empty? 7 | sign_in(resource) 8 | end 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require File.expand_path('../config/application', __FILE__) 2 | 3 | Rails.application.load_tasks 4 | 5 | # Don't dump database structure in deployed environments 6 | Rake::Task['db:structure:dump'].clear if Rails.env.in? %w(production staging) 7 | 8 | # Log to console in development 9 | if Rails.env.development? 10 | Rails.logger = Logger.new(STDOUT) 11 | end 12 | -------------------------------------------------------------------------------- /app/assets/javascripts/components/data_show_if_selected.coffee: -------------------------------------------------------------------------------- 1 | $ -> 2 | $('[data-show-if-selected]').each -> 3 | $el = $(@) 4 | [selector, valueToMatch] = $(@).data('show-if-selected').split('|') 5 | $input = $(selector) 6 | showHide = -> $el[if $input.val() in valueToMatch.split(',') then 'show' else 'hide']() 7 | $input.on 'change.show_if_selected', $input, showHide 8 | -------------------------------------------------------------------------------- /app/views/mailer/question_deadline.html.erb: -------------------------------------------------------------------------------- 1 | <%= render('greeting') %> 2 | 3 |

    4 | <%= t('mailer.question_deadline.body_html', href: opportunity_url(@opportunity), title: @opportunity.title) %> 5 |

    6 | 7 |

    8 | 9 | <%= t('ask_question') %> → 10 | 11 |

    12 | -------------------------------------------------------------------------------- /app/views/mailer/submission_deadline.html.erb: -------------------------------------------------------------------------------- 1 | <%= render('greeting') %> 2 | 3 |

    4 | <%= t('mailer.submission_deadline.body_html', href: opportunity_url(@opportunity), title: @opportunity.title) %> 5 |

    6 | 7 |

    8 | 9 | <%= t('submit_proposal') %> → 10 | 11 |

    12 | -------------------------------------------------------------------------------- /app/models/category.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: categories 4 | # 5 | # id :integer not null, primary key 6 | # name :string 7 | # created_at :datetime not null 8 | # updated_at :datetime not null 9 | # 10 | 11 | class Category < ActiveRecord::Base 12 | has_and_belongs_to_many :opportunities 13 | has_and_belongs_to_many :users 14 | end 15 | -------------------------------------------------------------------------------- /app/views/mailer/approval_request.html.erb: -------------------------------------------------------------------------------- 1 | <%= render('greeting') %> 2 | 3 |

    4 | <%= t('mailer.approval_request.body_html', creator: @creator.name, href: opportunity_url(@opportunity), title: @opportunity.title) %> 5 |

    6 | 7 |

    8 | 9 | <%= t('view_opportunity') %> → 10 | 11 |

    12 | -------------------------------------------------------------------------------- /spec/models/category_spec.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: categories 4 | # 5 | # id :integer not null, primary key 6 | # name :string 7 | # created_at :datetime not null 8 | # updated_at :datetime not null 9 | # 10 | 11 | require 'spec_helper' 12 | 13 | describe Category do 14 | subject { build(:category) } 15 | it { should be_valid } 16 | end 17 | -------------------------------------------------------------------------------- /app/inputs/datetime_picker_input.rb: -------------------------------------------------------------------------------- 1 | class DatetimePickerInput < SimpleForm::Inputs::StringInput 2 | def initialize(*) 3 | super 4 | 5 | # Format value as iso8601 for javascript new Date() compatibility 6 | if object && (value = object.send(attribute_name)) 7 | input_html_options[:value] = value.iso8601 8 | end 9 | 10 | input_html_options[:placeholder] = 'MM/DD/YYYY' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/components/date_range.scss: -------------------------------------------------------------------------------- 1 | @import 'theme/includes'; 2 | 3 | .date_range { 4 | input { 5 | display: inline; 6 | width: 11rem; 7 | } 8 | 9 | span { 10 | font-size: $fontSmaller; 11 | color: $darkerGray; 12 | } 13 | 14 | input, span { 15 | margin-right: $rhythm; 16 | 17 | &:last-child { 18 | margin-right: 0; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/overrides/hero.scss: -------------------------------------------------------------------------------- 1 | @import 'theme/includes'; 2 | 3 | .hero { 4 | background: $primaryColor url(asset-path('hero.jpg')) no-repeat center; 5 | background-size: cover; 6 | text-align: center; 7 | padding-top: $lineHeight * 5; 8 | h1 { 9 | margin: 0 0 ($rhythm * 2); 10 | } 11 | p { 12 | margin: 0 auto $lineHeight; 13 | max-width: 30rem; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /spec/models/department_spec.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: departments 4 | # 5 | # id :integer not null, primary key 6 | # name :string 7 | # created_at :datetime not null 8 | # updated_at :datetime not null 9 | # 10 | 11 | require 'spec_helper' 12 | 13 | describe Department do 14 | subject { build(:department) } 15 | it { should be_valid } 16 | end 17 | -------------------------------------------------------------------------------- /app/views/opportunities/_vendor_actions.html.erb: -------------------------------------------------------------------------------- 1 | <% if policy(@opportunity).submit? || policy(@opportunity).ask_question? %> 2 |
    3 | <%= render('opportunities/actions/submit') %> 4 | <%= render('opportunities/actions/ask_question') %> 5 |
    6 | 7 | <%= render('opportunities/actions/submission_instructions') %> 8 | <% end %> 9 | 10 | <%= render('opportunities/contact_info') %> 11 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/javascripts/selectize_monkeypatch.coffee: -------------------------------------------------------------------------------- 1 | # Monkeypatch Selectize to not use "item" class, which conlflits with dvl-core... 2 | oldSetupTemplates = Selectize::setupTemplates 3 | 4 | Selectize::setupTemplates = -> 5 | oldSetupTemplates.apply(@, arguments) 6 | @settings.render.item = (data, escape) -> 7 | """ 8 |
    #{escape(data[@settings.labelField])}
    9 | """ 10 | -------------------------------------------------------------------------------- /app/models/department.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: departments 4 | # 5 | # id :integer not null, primary key 6 | # name :string 7 | # created_at :datetime not null 8 | # updated_at :datetime not null 9 | # 10 | 11 | class Department < ActiveRecord::Base 12 | has_many :opportunities, dependent: :nullify 13 | 14 | default_scope -> { order('LOWER(name)') } 15 | end 16 | -------------------------------------------------------------------------------- /app/views/opportunities/actions/_review_submissions.html.erb: -------------------------------------------------------------------------------- 1 | <% if policy(@opportunity).review_submissions? %> 2 |
  • 3 | <% if @opportunity.submission_adapter.view_proposals_url %> 4 | 5 | <%= @opportunity.submission_adapter.view_proposals_link_text.presence || t('view_proposals') %> 6 | 7 | <% end %> 8 |
  • 9 | <% end %> 10 | -------------------------------------------------------------------------------- /spec/i18n_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'i18n/tasks' 3 | 4 | RSpec.describe 'I18n' do 5 | let(:i18n) { I18n::Tasks::BaseTask.new } 6 | let(:missing_keys) { i18n.missing_keys } 7 | let(:unused_keys) { i18n.unused_keys } 8 | 9 | it 'does not have missing keys' do 10 | expect(missing_keys).to be_empty 11 | end 12 | 13 | it 'does not have unused keys' do 14 | expect(unused_keys).to be_empty 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/components/search_result_count.scss: -------------------------------------------------------------------------------- 1 | @import 'theme/includes'; 2 | 3 | .search_result_count { 4 | display: block; 5 | background: $lightestGray; 6 | font-size: $fontSmaller; 7 | white-space: nowrap; 8 | padding: $rhythm ($rhythm * 2); 9 | border-radius: $radius; 10 | position: relative; 11 | top: $rhythm / -2; 12 | @media screen and (max-width: $lapWidth - 1) { 13 | display: none; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/views/opportunities/_timeline.html.erb: -------------------------------------------------------------------------------- 1 | <% if opportunity_timeline_events.present? %> 2 | 10 | <% end %> 11 | -------------------------------------------------------------------------------- /bin/guard: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'guard' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('guard', 'guard') 17 | -------------------------------------------------------------------------------- /spec/mailers/previews/custom_devise_mailer_preview.rb: -------------------------------------------------------------------------------- 1 | class CustomDeviseMailerPreview < ActionMailer::Preview 2 | def confirmation_instructions 3 | CustomDeviseMailer.confirmation_instructions( 4 | User.first || FactoryGirl.create(:user), 5 | 'xxx' 6 | ) 7 | end 8 | 9 | def reset_password_instructions 10 | CustomDeviseMailer.reset_password_instructions( 11 | User.first || FactoryGirl.create(:user), 12 | 'xxx' 13 | ) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/factories/saved_searches.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: saved_searches 4 | # 5 | # id :integer not null, primary key 6 | # user_id :integer 7 | # search_params :text 8 | # created_at :datetime not null 9 | # updated_at :datetime not null 10 | # 11 | # Indexes 12 | # 13 | # index_saved_searches_on_user_id (user_id) 14 | # 15 | 16 | FactoryGirl.define do 17 | factory :saved_search do 18 | user 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/views/home/_hero.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    <%= t('.title') %>

    4 | <% if signed_in? %> 5 |

    <%= t('.hint_signed_in') %>

    6 | <%= t('search_for_opportunities') %> → 7 | <% else %> 8 |

    <%= t('.hint') %>

    9 | <%= t('.cta') %> 10 | <% end %> 11 |
    12 |
    13 | -------------------------------------------------------------------------------- /app/views/opportunities/actions/_ask_question.html.erb: -------------------------------------------------------------------------------- 1 | <% if policy(@opportunity).ask_question? %> 2 |
  • 3 | <%= t('ask_question') %> 4 |
  • 5 | <% elsif OpportunityPolicy.new(User.new, @opportunity).ask_question? %> 6 |
  • 7 | <%= t('ask_question') %> 8 |
  • 9 | <% end %> 10 | -------------------------------------------------------------------------------- /app/views/mailer/question_answered.html.erb: -------------------------------------------------------------------------------- 1 | <%= render('greeting') %> 2 | 3 |

    4 | <%= t('mailer.question_answered.body_html', href: opportunity_url(@question.opportunity), title: @question.opportunity.title) %> 5 |

    6 | 7 |

    8 | <%= format_textarea_input(@question.answer_text) %> 9 |

    10 | 11 |

    12 | <%= t('view_opportunity') %> → 13 |

    14 | -------------------------------------------------------------------------------- /spec/support/database_cleaner.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.before(:suite) do 3 | DatabaseCleaner.clean_with(:truncation) 4 | end 5 | 6 | config.before(:each) do 7 | DatabaseCleaner.strategy = :transaction 8 | end 9 | 10 | config.before(:each, js: true) do 11 | DatabaseCleaner.strategy = :truncation 12 | end 13 | 14 | config.before(:each) do 15 | DatabaseCleaner.start 16 | end 17 | 18 | config.after(:each) do 19 | DatabaseCleaner.clean 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/views/custom_devise_mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

    Hello <%= @resource.name %>!

    2 | 3 |

    Someone has requested a link to change your password. You can do this through the link below.

    4 | 5 |

    <%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token), class: 'btn-primary' %>

    6 | 7 |

    If you didn't request this, please ignore this email.

    8 |

    Your password won't change until you access the link above and create a new one.

    9 | -------------------------------------------------------------------------------- /app/views/opportunities/_attachments.html.erb: -------------------------------------------------------------------------------- 1 | <% if @opportunity.attachments.present? %> 2 |
    3 |
    4 |

    <%= t('attachments') %>

    5 |
    6 | 7 |
    8 | <% @opportunity.attachments.each do |attachment| %> 9 | <%= render partial: 'view_attachment', locals: { attachment: attachment } %> 10 | <% end %> 11 |
    12 |
    13 | <% end %> 14 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/branding.scss: -------------------------------------------------------------------------------- 1 | $navBackground: #2E2334; 2 | 3 | $primaryColor: #2E2334; 4 | $secondaryColor: #ED8442; 5 | 6 | $fontFamilyDisplay: 'Passion One', 'Helvetica Neue', Arial, sans-serif; 7 | $fontFamilyDefault: 'Roboto', 'HelveticaNeue-Light', Helvetica, Arial, sans-serif; 8 | $fontFamilyMonospace: 'Monaco', 'Lucida Console', monospace; 9 | 10 | // Smooth text looks best against our $primaryColor 11 | 12 | .navbar a, 13 | .button.primary { 14 | @include font_smoothing; 15 | } 16 | -------------------------------------------------------------------------------- /app/assets/javascripts/components/submission_adapters.coffee: -------------------------------------------------------------------------------- 1 | disableInvisibleInputs = -> 2 | $('[data-adapter-name]').each -> 3 | if $(@).is(':visible') 4 | $(@).find(':input').prop('disabled', false) 5 | else 6 | $(@).find(':input').prop('disabled', true) 7 | 8 | $ -> 9 | $('#opportunity_submission_adapter_name').on 'change', -> 10 | $('[data-adapter-name]').hide() 11 | $("[data-adapter-name=\"#{$(@).val()}\"]").show() 12 | disableInvisibleInputs() 13 | 14 | disableInvisibleInputs() 15 | -------------------------------------------------------------------------------- /app/helpers/pick_helper.rb: -------------------------------------------------------------------------------- 1 | module PickHelper 2 | def pick(obj, *keys) 3 | stringified_keys = keys.map(&:to_s) 4 | 5 | {}.tap do |h| 6 | if obj.is_a?(Hash) 7 | obj.each do |key, value| 8 | h[key.to_sym] = value if stringified_keys.include?(key.to_s) 9 | end 10 | else 11 | stringified_keys.each do |key| 12 | if obj.respond_to?(key) 13 | h[key.to_sym] = obj.send(key) 14 | end 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/views/mailer/question_asked.html.erb: -------------------------------------------------------------------------------- 1 | <%= render('greeting') %> 2 | 3 |

    4 | <%= t('mailer.question_asked.body_html', href: opportunity_url(@question.opportunity), title: @question.opportunity.title) %> 5 |

    6 | 7 |

    8 | <%= format_textarea_input(@question.question_text) %> 9 |

    10 | 11 |

    12 | '><%= t('answer_question') %> → 13 |

    14 | -------------------------------------------------------------------------------- /app/views/users/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for(:page_title, 'Forgot your password?') %> 2 | <% content_for(:main_container_class, 'container_tiny') %> 3 | 4 | 7 | 8 | <%= simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> 9 | <%= f.input :email, required: true, autofocus: true %> 10 | <%= f.button :submit, "Send me reset password instructions", class: 'primary' %> 11 | <% end %> 12 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m)) 11 | Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq } 12 | gem 'spring', match[1] 13 | require 'spring/binstub' 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | 8 | Rails.application.config.secret_token = DispatchConfiguration.secret_token 9 | Rails.application.config.secret_key_base = DispatchConfiguration.secret_key_base 10 | -------------------------------------------------------------------------------- /spec/models/saved_search_spec.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: saved_searches 4 | # 5 | # id :integer not null, primary key 6 | # user_id :integer 7 | # search_params :text 8 | # created_at :datetime not null 9 | # updated_at :datetime not null 10 | # 11 | # Indexes 12 | # 13 | # index_saved_searches_on_user_id (user_id) 14 | # 15 | 16 | require 'spec_helper' 17 | 18 | describe SavedSearch do 19 | subject { build(:saved_search) } 20 | it { should be_valid } 21 | end 22 | -------------------------------------------------------------------------------- /app/views/opportunities/_pending_item.html.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | 3 | <%= opportunity.title %> 4 | 5 | <% if opportunity.approved? %> 6 | <%= t('will_publish_at_time', timestamp: long_timestamp(opportunity.publish_at)).html_safe %> 7 | <% else %> 8 | <%= t('created_by_user_at_time', user: opportunity.created_by_user.name, timestamp: long_timestamp(opportunity.created_at)).html_safe %> 9 | <% end %> 10 | 11 | 12 |
  • 13 | -------------------------------------------------------------------------------- /spec/features/sign_in_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Signing in' do 4 | let!(:user) { create(:user, password: 'password') } 5 | 6 | it 'works properly' do 7 | visit root_path 8 | click_link t('sign_in') 9 | fill_in 'Email address', with: user.email 10 | fill_in 'Password', with: 'password' 11 | click_button t('sign_in') 12 | expect(page.body).to include t('devise.sessions.signed_in') 13 | click_link t('sign_out') 14 | expect(page.body).to include t('devise.sessions.signed_out') 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/views/opportunities/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for(:page_key, 'opportunities-new') %> 2 | <% content_for(:page_title, t('post_an_opportunity')) %> 3 | <% content_for(:main_container_class, 'container_tiny') %> 4 | 5 | 8 | 9 | <%= simple_form_for @opportunity do |f| %> 10 | <%= f.input :title, label: false, input_html: { class: 'large', 'aria-label' => t('.whats_the_title') } %> 11 | <%= f.button :button, t('get_started'), class: 'primary' %> 12 | <% end %> 13 | -------------------------------------------------------------------------------- /app/helpers/formatting_helper.rb: -------------------------------------------------------------------------------- 1 | module FormattingHelper 2 | def format_textarea_input(x) 3 | Rinku.auto_link(simpler_format(x), :all, "rel='nofollow'").html_safe 4 | end 5 | 6 | def simpler_format(text) 7 | text = '' if text.nil? 8 | text = text.dup 9 | text = CGI.escapeHTML(text) 10 | text = text.to_str 11 | text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n 12 | text.gsub!(/\n\n+/, '

    ') # 2+ newline -> br br 13 | text.gsub!(/([^\n]\n)(?=[^\n])/, '\1
    ') # 1 newline -> br 14 | text.html_safe 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/views/opportunities/actions/_submit.html.erb: -------------------------------------------------------------------------------- 1 | <% if policy(@opportunity).submit? %> 2 | <% if @opportunity.submission_adapter.submission_page %> 3 |
  • 4 | <%= t('submit_proposal') %> 5 |
  • 6 | <% elsif @opportunity.submission_adapter.submit_proposals_url %> 7 |
  • 8 | <%= t('submit_proposal') %> 9 |
  • 10 | <% end %> 11 | <% end %> 12 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | experimental: 2 | notify: 3 | branches: 4 | only: 5 | - master 6 | 7 | machine: 8 | environment: 9 | GEMNASIUM_TESTSUITE: 'script/cibuild' 10 | GEMNASIUM_PROJECT_SLUG: 'github.com/dobtco/dispatch' 11 | 12 | dependencies: 13 | cache_directories: 14 | - "public/assets" 15 | - "tmp/cache/assets" 16 | 17 | database: 18 | override: 19 | - mv config/database.ci.yml config/database.yml 20 | - bundle exec rake db:setup 21 | post: 22 | - "RAILS_ENV=test bundle exec rake assets:precompile assets:clean[0]" 23 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/components/opportunity_actions.scss: -------------------------------------------------------------------------------- 1 | @import 'theme/includes'; 2 | 3 | .opportunity_actions { 4 | margin-left: - $rhythm; 5 | font-size: 0; 6 | li { 7 | font-size: 100%; 8 | display: inline-block; 9 | padding-left: $rhythm; 10 | position: relative; 11 | width: 50%; 12 | @media screen and (min-width: $lapWidth) { 13 | width: 100%; 14 | } 15 | @media screen and (min-width: $maxWidth) { 16 | width: 50%; 17 | } 18 | } 19 | a { 20 | margin-bottom: $rhythm; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile ~/.gitignore_global 6 | 7 | # Ignore bundler config 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | 13 | # Ignore all logfiles and tempfiles. 14 | /log 15 | /tmp 16 | 17 | # SimpleCov reports 18 | coverage 19 | 20 | # uploads 21 | public/uploads 22 | -------------------------------------------------------------------------------- /app/assets/javascripts/components/datetime_picker.coffee: -------------------------------------------------------------------------------- 1 | format = 'M/D/YYYY h:mma' 2 | 3 | $ -> 4 | $('input.datetime_picker').each -> 5 | $input = $(@) 6 | $hiddenInput = $("") 7 | $hiddenInput.insertAfter($input) 8 | $input.attr('name', null) 9 | rome( 10 | @, 11 | inputFormat: format, 12 | timeFormat: "h:mma", 13 | timeInterval: 3600 # hour 14 | initialValue: new Date($input.val()) 15 | ).on 'data', -> 16 | $hiddenInput.val moment($input.val(), format).toDate() 17 | -------------------------------------------------------------------------------- /app/views/home/_about.html.erb: -------------------------------------------------------------------------------- 1 | <% # This page shouldn't be translated, since it will vary from site to site. %> 2 | 3 |
    4 |
    5 |

    About <%= DispatchConfiguration.site_title %>

    6 |
    7 |
    8 | <%= DispatchConfiguration.site_title %> is a website that lists contracting opportunities 9 | from <%= DispatchConfiguration.agency_name %>. It's powered by an open-source project from The Department of Better Technology.
    10 |
    11 | -------------------------------------------------------------------------------- /app/views/users/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for(:page_title, 'Resend confirmation instructions') %> 2 | <% content_for(:main_container_class, 'container_tiny') %> 3 | 4 | 7 | 8 | <%= simple_form_for resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post } do |f| %> 9 | <%= f.full_error :confirmation_token %> 10 | <%= f.input :email, required: true, autofocus: true %> 11 | <%= f.button :submit, "Resend", class: 'primary' %> 12 | <% end %> 13 | -------------------------------------------------------------------------------- /app/views/submission_adapters/screendoor/_submit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :extra_css do %> 2 | 3 | <% end %> 4 | 5 | <% content_for :extra_js do %> 6 | 7 | 11 | <% end %> 12 | 13 |
    This form requires JavaScript to complete.
    14 | -------------------------------------------------------------------------------- /spec/factories/departments.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: departments 4 | # 5 | # id :integer not null, primary key 6 | # name :string 7 | # created_at :datetime not null 8 | # updated_at :datetime not null 9 | # 10 | 11 | FactoryGirl.define do 12 | factory :department do 13 | sequence(:name) do |i| 14 | names = [ 15 | 'Office of Management and Budget', 16 | 'Office of Innovation and Technology', 17 | "Mayor's Office" 18 | ] 19 | 20 | names[i % names.length] 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /app/models/audit.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: audits 4 | # 5 | # id :integer not null, primary key 6 | # user_id :integer not null 7 | # event :string 8 | # data :text 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # 12 | # Indexes 13 | # 14 | # index_audits_on_event (event) 15 | # index_audits_on_user_id (user_id) 16 | # index_audits_on_user_id_and_event (user_id,event) 17 | # 18 | 19 | class Audit < ActiveRecord::Base 20 | belongs_to :user 21 | serialize :data, Hash 22 | end 23 | -------------------------------------------------------------------------------- /app/views/users/registrations/_my_opportunities.erb: -------------------------------------------------------------------------------- 1 | <% if (opps = current_user.created_opportunities.order_by_recently_updated).present? %> 2 |
    3 |

    <%= t('my_opportunities') %>

    4 |
    5 | 6 | 16 | <% end %> 17 | -------------------------------------------------------------------------------- /app/models/saved_search.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: saved_searches 4 | # 5 | # id :integer not null, primary key 6 | # user_id :integer 7 | # search_params :text 8 | # created_at :datetime not null 9 | # updated_at :datetime not null 10 | # 11 | # Indexes 12 | # 13 | # index_saved_searches_on_user_id (user_id) 14 | # 15 | 16 | class SavedSearch < ActiveRecord::Base 17 | belongs_to :user 18 | serialize :search_params, Hash 19 | 20 | PERMITTED_SEARCH_PARAMS = [ 21 | :text, 22 | :status, 23 | :category_ids 24 | ].freeze 25 | end 26 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/dvl_core.scss: -------------------------------------------------------------------------------- 1 | @import 'theme/includes'; 2 | @import 'dvl/core'; 3 | @import 'dvl/components/navbar'; 4 | @import 'dvl/components/flashes'; 5 | @import 'dvl/components/hero'; 6 | @import 'dvl/components/page_header'; 7 | @import 'dvl/components/page_subheader'; 8 | @import 'dvl/components/sidebar_sub_actions'; 9 | @import 'dvl/components/selectize'; 10 | @import 'dvl/components/alerts'; 11 | @import 'dvl/components/sidebar_data'; 12 | @import 'dvl/components/tabs'; 13 | @import 'dvl/components/blank_slate'; 14 | @import 'dvl/components/footer'; 15 | @import 'dvl/components/pagination'; 16 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/overrides/page_header.scss: -------------------------------------------------------------------------------- 1 | @import 'theme/includes'; 2 | 3 | .page_header_simple h2 { 4 | text-align: center; 5 | float: none; 6 | margin: 0 0 ($rhythm * 1.5); 7 | } 8 | 9 | .page_header_action { 10 | display: table; 11 | padding-bottom: $rhythm * 1.5; 12 | h2, .page_header_action_button { 13 | float: none; 14 | display: table-cell; 15 | } 16 | h2 { 17 | width: 100%; 18 | padding-right: $rhythm * 2; 19 | } 20 | .page_header_action_button { 21 | width: 1px; 22 | .button { 23 | position: relative; 24 | top: $rhythm; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spec/features/home_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Home' do 4 | describe 'recent opportunities' do 5 | let!(:visible_opportunity) { create(:opportunity, :published, :approved) } 6 | let!(:not_visible_opportunity) { create(:opportunity, :published) } 7 | 8 | it 'shows posted opportunities only' do 9 | visit root_path 10 | 11 | expect(page).to have_selector( 12 | %([href="#{opportunity_path(visible_opportunity)}"]) 13 | ) 14 | 15 | expect(page).to_not have_selector( 16 | %([href="#{opportunity_path(not_visible_opportunity)}"]) 17 | ) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/submission_adapters/screendoor.rb: -------------------------------------------------------------------------------- 1 | module SubmissionAdapters 2 | class Screendoor < SubmissionAdapters::Base 3 | self.select_text = 'Screendoor' 4 | self.submission_page = true 5 | 6 | def view_proposals_url 7 | "https://screendoor.dobt.co/projects/#{embed_token}/admin" 8 | end 9 | 10 | def view_proposals_link_text 11 | "Review submissions ".html_safe 12 | end 13 | 14 | def valid? 15 | embed_token.present? 16 | end 17 | 18 | private 19 | 20 | def embed_token 21 | @opportunity.submission_adapter_data['embed_token'] 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/views/home/_recent_opportunities.html.erb: -------------------------------------------------------------------------------- 1 | <% if @recent_opportunities.present? %> 2 |
    3 |

    <%= t('.title') %>

    4 | 14 | 15 |

    <%= t('.see_more') %>

    16 |
    17 | <% end %> 18 | -------------------------------------------------------------------------------- /docs/developing_dispatch_core.md: -------------------------------------------------------------------------------- 1 | Developing Dispatch Core 2 | --- 3 | 4 | > Developing "Dispatch Core" is different than [customizing](customization.md) your instance of Dispatch. Before making changes to the core application, check to see if your use case is covered in our [customization docs](customization.md) first. 5 | 6 | ## Some general rules of thumb 7 | 8 | - `dvl-core` is the default theme, so the views in `app/views/` use class names for that theme 9 | - Strings should be internationalized. Run `i18n-tasks normalize` before committing in order to sort the keys inside of `en.yml` properly 10 | - Don't add too much JavaScript -- this will make it harder to customize the app 11 | -------------------------------------------------------------------------------- /app/views/opportunities/feed.xml.builder: -------------------------------------------------------------------------------- 1 | xml.instruct! :xml, version: "1.0" 2 | xml.rss version: "2.0" do 3 | xml.channel do 4 | xml.title "Opportunities - #{DispatchConfiguration.site_title}" 5 | xml.description "Feed of opportunities posted to #{DispatchConfiguration.site_title}." 6 | xml.link opportunities_url 7 | 8 | for opportunity in @opportunities 9 | xml.item do 10 | xml.title opportunity.title 11 | xml.description opportunity.description 12 | xml.pubDate opportunity.updated_at.to_s(:rfc822) 13 | xml.link opportunity_url(opportunity) 14 | xml.guid opportunity_url(opportunity) 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /config/initializers/carrierwave.rb: -------------------------------------------------------------------------------- 1 | CarrierWave.configure do |config| 2 | if DispatchConfiguration.upload_storage == 'aws' 3 | config.storage = :aws 4 | config.cache_dir = Rails.root.join('tmp/uploads') 5 | config.aws_credentials = { 6 | access_key_id: DispatchConfiguration.aws_key, 7 | secret_access_key: DispatchConfiguration.aws_secret, 8 | region: DispatchConfiguration.aws_region 9 | } 10 | config.aws_bucket = DispatchConfiguration.aws_bucket 11 | config.aws_attributes = { 12 | cache_control: 'max-age=315576000' 13 | } 14 | config.aws_authenticated_url_expiration = 60 * 60 # one hour 15 | else 16 | config.storage = :file 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/views/opportunities/_subscribe_button.html.erb: -------------------------------------------------------------------------------- 1 | <% if policy(@opportunity).subscribe? %> 2 | 3 | <% if current_user.opportunities.include?(@opportunity) %> 4 | 5 | <%= t('reminded') %> 6 | <% else %> 7 | 8 | <%= t('remind_me') %> 9 | <% end %> 10 | 11 | <% elsif OpportunityPolicy.new(User.new, @opportunity).subscribe? %> 12 | <% # @todo redirect after login %> 13 | <%= t('remind_me') %> 14 | <% end %> 15 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/theme.rb: -------------------------------------------------------------------------------- 1 | def require_if_exists(path) 2 | require(path) if File.exist?(path) 3 | end 4 | 5 | # Require theme's simple_form configuration 6 | require_if_exists DispatchConfiguration.theme_path.join('simple_form.rb') 7 | 8 | # Add theme assets to the beginning of the sprockets load path 9 | Rails.configuration.assets.paths = 10 | Dir[DispatchConfiguration.theme_path.join("assets/*")] + 11 | Rails.configuration.assets.paths 12 | 13 | # Add theme i18n 14 | Rails.configuration.i18n.load_path += 15 | Dir[DispatchConfiguration.theme_path.join('locales/**/*')] 16 | 17 | # Load theme's submission adapters 18 | Dir[DispatchConfiguration.theme_path.join('submission_adapters/**/*')].each do |f| 19 | require f 20 | end 21 | -------------------------------------------------------------------------------- /lib/submission_adapters/email.rb: -------------------------------------------------------------------------------- 1 | module SubmissionAdapters 2 | class Email < SubmissionAdapters::Base 3 | self.select_text = 'Email' 4 | 5 | def submit_proposals_instructions 6 | %( 7 | Proposals for this opportunity should be sent by email to 8 | #{submit_to_name}. 9 | ).squish.html_safe 10 | end 11 | 12 | def valid? 13 | submit_to_email.present? && 14 | submit_to_name.present? 15 | end 16 | 17 | private 18 | 19 | def submit_to_email 20 | @opportunity.submission_adapter_data['email'] 21 | end 22 | 23 | def submit_to_name 24 | @opportunity.submission_adapter_data['name'] 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/views/application/_footer.html.erb: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /app/views/opportunities/_view_attachment.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <% if attachment.has_thumbnail? %> 3 | 4 | 5 | 6 | <% else %> 7 | 8 | 9 | <%= attachment.upload.friendly_file_type %> 10 | 11 | <% end %> 12 | 13 | 14 | <%= attachment.upload.raw_filename %> 15 | <%= number_to_human_size(attachment.file_size_bytes)%> 16 | 17 | 18 | -------------------------------------------------------------------------------- /spec/models/question_spec.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: questions 4 | # 5 | # id :integer not null, primary key 6 | # opportunity_id :integer 7 | # asked_by_user_id :integer 8 | # answered_by_user_id :integer 9 | # question_text :text 10 | # answer_text :text 11 | # answered_at :datetime 12 | # deleted_at :datetime 13 | # created_at :datetime not null 14 | # updated_at :datetime not null 15 | # 16 | # Indexes 17 | # 18 | # index_questions_on_opportunity_id (opportunity_id) 19 | # 20 | 21 | require 'spec_helper' 22 | 23 | describe Question do 24 | subject { build(:question) } 25 | it { should be_valid } 26 | end 27 | -------------------------------------------------------------------------------- /app/views/opportunities/_contact_info.html.erb: -------------------------------------------------------------------------------- 1 | <% if @opportunity.contact_info? %> 2 |
    3 |
    4 |

    <%= t('contact') %>

    5 |
    6 |
    7 | <%= @opportunity.contact_name || @opportunity.contact_email %> 8 | 9 | <% if @opportunity.contact_email.present? %> 10 | <%= @opportunity.contact_email %> 11 | <% end %> 12 | 13 | <% if @opportunity.contact_phone.present? %> 14 | / 15 | <%= @opportunity.contact_phone %> 16 | <% end %> 17 | 18 |
    19 |
    20 | <% end %> 21 | -------------------------------------------------------------------------------- /config/schedule.rb: -------------------------------------------------------------------------------- 1 | # Use this file to easily define all of your cron jobs. 2 | # 3 | # It's helpful, but not entirely necessary to understand cron before proceeding. 4 | # http://en.wikipedia.org/wiki/Cron 5 | 6 | # Example: 7 | # 8 | # set :output, "/path/to/my/cron_log.log" 9 | # 10 | # every 2.hours do 11 | # command "/usr/bin/some_great_command" 12 | # runner "MyModel.some_method" 13 | # rake "some:great:rake:task" 14 | # end 15 | # 16 | # every 4.days do 17 | # runner "AnotherModel.prune_old_records" 18 | # end 19 | 20 | # Learn more: http://github.com/javan/whenever 21 | 22 | every 1.week do 23 | runner 'QueueUserSearchResultsJob.perform_later' 24 | end 25 | 26 | every 1.hour do 27 | runner 'SendDeadlineRemindersJob.perform_later' 28 | end 29 | -------------------------------------------------------------------------------- /lib/dispatch_configuration.rb: -------------------------------------------------------------------------------- 1 | module DispatchConfiguration 2 | class << self 3 | def theme_path 4 | Rails.root.join("themes/#{theme}") 5 | end 6 | 7 | def method_missing(name) 8 | ENV[name.to_s.upcase] || 9 | read_configuration[name.to_s.upcase] 10 | end 11 | 12 | private 13 | 14 | def read_configuration 15 | @configuration ||= YAML.safe_load(File.read(config_file)) 16 | end 17 | 18 | def config_file 19 | if File.exist?(Rails.root.join('config.yml')) 20 | Rails.root.join('config.yml') 21 | else 22 | # For development modes and the like, just use the defaults in the 23 | # example file 24 | Rails.root.join('config.yml.example') 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /config/environments/_smtp_env_vars.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | config.action_mailer.delivery_method = :smtp 3 | config.action_mailer.smtp_settings = { 4 | address: DispatchConfiguration.smtp_address, 5 | port: (DispatchConfiguration.smtp_port.presence || 465).to_i, 6 | enable_starttls_auto: DispatchConfiguration.smtp_starttls_auto == '1', 7 | user_name: DispatchConfiguration.smtp_user.presence, 8 | password: DispatchConfiguration.smtp_password.presence, 9 | authentication: DispatchConfiguration.smtp_authentication.presence. 10 | try(:to_sym), 11 | domain: DispatchConfiguration.smtp_domain.presence || 12 | DispatchConfiguration.base_domain, 13 | ssl: DispatchConfiguration.smtp_ssl == '1' 14 | } 15 | end 16 | -------------------------------------------------------------------------------- /themes/dvl-core/assets/stylesheets/theme/layout/application.scss: -------------------------------------------------------------------------------- 1 | @import 'theme/includes'; 2 | 3 | .container { 4 | @include container; 5 | 6 | &.container_small { 7 | max-width: 50rem; 8 | } 9 | 10 | &.container_tiny { 11 | max-width: 35rem; 12 | } 13 | } 14 | 15 | .main_container { 16 | padding-top: $rhythm * 3; 17 | } 18 | 19 | .opportunity_section { 20 | margin-bottom: $rhythm * 4; 21 | 22 | form { 23 | max-width: 36rem; 24 | } 25 | } 26 | 27 | .page_subheader_simple { 28 | margin-top: $lineHeight; 29 | h3 { 30 | text-align: center; 31 | float: none; 32 | display: block; 33 | } 34 | } 35 | 36 | .footer { 37 | margin-top: $lineHeight * 2; 38 | } 39 | 40 | th a { 41 | color: $darkerGray; 42 | white-space: nowrap; 43 | } 44 | -------------------------------------------------------------------------------- /app/uploaders/base_uploader.rb: -------------------------------------------------------------------------------- 1 | class BaseUploader < CarrierWave::Uploader::Base 2 | self.aws_acl = :private 3 | 4 | def model_class_name 5 | model.class.to_s 6 | end 7 | 8 | def store_dir 9 | "uploads/#{store_digest}" 10 | end 11 | 12 | def store_digest 13 | Digest::SHA2.hexdigest( 14 | "#{model_class_name.underscore}-#{mounted_as}-#{model.id}" 15 | ).first(32) 16 | end 17 | 18 | def raw_filename 19 | @model.read_attribute(mounted_as) 20 | end 21 | 22 | def friendly_file_type 23 | if (ext = File.extname(raw_filename)).present? 24 | ext[1..-1].upcase # Remove '.' from string 25 | else 26 | '?' 27 | end 28 | end 29 | 30 | def download_url 31 | url(response_content_disposition: 'attachment') 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /app/views/users/registrations/_edit_password.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for(:page_title, 'Change password') %> 2 | <% content_for(:main_container_class, 'container_tiny') %> 3 | 4 | 7 | 8 | <%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> 9 | 10 | 11 | <%= f.input :current_password, label: "Current password", required: true, autofocus: true %> 12 | <%= f.input :password, label: "New password", required: true %> 13 | <%= f.input :password_confirmation, label: "Confirm your new password", required: true %> 14 | 15 | <%= f.button :button, 'Change your password' %> 16 | <% end %> 17 | -------------------------------------------------------------------------------- /app/models/attachment.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: attachments 4 | # 5 | # id :integer not null, primary key 6 | # opportunity_id :integer 7 | # upload :string 8 | # content_type :string 9 | # file_size_bytes :integer 10 | # has_thumbnail :boolean default(FALSE), not null 11 | # deleted_at :datetime 12 | # created_at :datetime not null 13 | # updated_at :datetime not null 14 | # text_content :text 15 | # 16 | # Indexes 17 | # 18 | # index_attachments_on_opportunity_id (opportunity_id) 19 | # 20 | 21 | class Attachment < ActiveRecord::Base 22 | belongs_to :opportunity 23 | 24 | has_storage_unit 25 | 26 | mount_uploader :upload, AttachmentUploader 27 | validates :upload, presence: true 28 | end 29 | -------------------------------------------------------------------------------- /app/views/users/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for(:page_title, 'Change your password') %> 2 | <% content_for(:main_container_class, 'container_tiny') %> 3 | 4 | 7 | 8 | <%= simple_form_for resource, as: resource_name, url: password_path(resource_name), html: { method: :put } do |f| %> 9 | <%= f.input :reset_password_token, as: :hidden %> 10 | <%= f.full_error :reset_password_token %> 11 | <%= f.input :password, label: "New password", required: true, autofocus: true, hint: ("#{@minimum_password_length} characters minimum" if @minimum_password_length) %> 12 | <%= f.input :password_confirmation, label: "Confirm your new password", required: true %> 13 | <%= f.button :submit, "Change my password", class: 'primary' %> 14 | <% end %> 15 | -------------------------------------------------------------------------------- /spec/factories/categories.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: categories 4 | # 5 | # id :integer not null, primary key 6 | # name :string 7 | # created_at :datetime not null 8 | # updated_at :datetime not null 9 | # 10 | 11 | FactoryGirl.define do 12 | factory :category do 13 | sequence(:name) do |i| 14 | names = [ 15 | 'Business Intelligence & Analytics', 16 | 'Consulting', 17 | 'Creative Services', 18 | 'CRM', 19 | 'Data Management', 20 | 'Database', 21 | 'General', 22 | 'Mobile Apps', 23 | 'Process Improvement', 24 | 'Support', 25 | 'Web Apps', 26 | 'Web Design' 27 | ] 28 | 29 | names[i % names.length] 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /app/controllers/saved_searches_controller.rb: -------------------------------------------------------------------------------- 1 | class SavedSearchesController < ApplicationController 2 | include PickHelper 3 | 4 | before_action :authenticate_user! 5 | before_action :set_saved_search 6 | before_action :skip_authorization 7 | 8 | def create 9 | current_user.saved_searches.create(search_params: saved_search_params) 10 | redirect_to :back, success: t('filter_saved') 11 | end 12 | 13 | def destroy 14 | @saved_search.destroy 15 | redirect_to :back, info: t('filter_destroyed') 16 | end 17 | 18 | private 19 | 20 | def saved_search_params 21 | pick(params, *SavedSearch::PERMITTED_SEARCH_PARAMS) 22 | end 23 | 24 | def set_saved_search 25 | if params[:id] 26 | @saved_search = current_user.saved_searches.find(params[:id]) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/views/opportunities/edit/_submission_adapter_inputs.html.erb: -------------------------------------------------------------------------------- 1 | <% SubmissionAdapters.all_adapters.each do |x| %> 2 | <% if lookup_context.exists?(submission_adapter_edit_partial(x), nil, true) %> 3 |
    style='display:none'<% end %>> 4 | <%= f.simple_fields_for :submission_adapter_data, OpenStruct.new(f.object.submission_adapter_data) do |f| %> 5 | <%= render(partial: submission_adapter_edit_partial(x), locals: { f: f }) %> 6 | <% end %> 7 | 8 | <% if f.object.submission_adapter_name == x.to_adapter_name %> 9 |
    10 | <%= f.error :submission_adapter %> 11 |
    12 | <% end %> 13 |
    14 | <% end %> 15 | <% end %> 16 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # rubocop:disable all 2 | guard :rspec, all_on_start: false, all_after_pass: false, failed_mode: :focus, cmd: 'bin/rspec' do 3 | watch(%r{^spec/.+_spec\.rb$}) 4 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } 5 | watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } 6 | watch(%r{^app/controllers/api/(.+)\.rb$}) { |m| "spec/api/#{m[1]}_spec.rb" } 7 | watch(%r{^app/views/(.+)\.rb$}) { |m| "spec/views/#{m[1]}_spec.rb" } 8 | end 9 | 10 | guard :livereload do 11 | watch(%r{app/views/.+\.(erb)$}) 12 | watch(%r{themes/(.+)/assets/\w+/(.+)\.(scss)}) 13 | watch(%r{(app|vendor)(/assets/\w+/(.+)\.(scss))}) 14 | end 15 | 16 | guard :rubocop, all_on_start: false do 17 | watch(%r{.+\.rb$}) 18 | watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) } 19 | end 20 | -------------------------------------------------------------------------------- /spec/support/utilities.rb: -------------------------------------------------------------------------------- 1 | # Show file inputs that are hidden and replaced by a