Support for grape and onboarding via react
This commit is contained in:
parent
9c162d3f0d
commit
4c5b997d65
133 changed files with 14283 additions and 14420 deletions
12
.bootstraprc
Normal file
12
.bootstraprc
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"bootstrapVersion": 3,
|
||||
"styleLoaders": ["style", "css", "sass"],
|
||||
"extractStyles": true,
|
||||
"styles": {
|
||||
"mixins": true,
|
||||
"grid": true,
|
||||
"forms": true
|
||||
},
|
||||
|
||||
"scripts": false
|
||||
}
|
10
Gemfile
10
Gemfile
|
@ -112,7 +112,7 @@ gem 'countries'
|
|||
group :development do
|
||||
gem 'traceroute'
|
||||
gem 'debase'
|
||||
gem 'ruby-debug-ide', '0.6.0'
|
||||
gem 'ruby-debug-ide'
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
|
@ -155,3 +155,11 @@ gem 'foreman'
|
|||
group :production do
|
||||
gem 'rails_autoscale_agent'
|
||||
end
|
||||
|
||||
gem 'grape'
|
||||
gem 'grape-entity', git: 'https://github.com/ruby-grape/grape-entity.git', ref: '0e04aa561373b510c2486282979085eaef2ae663'
|
||||
gem 'grape-swagger'
|
||||
gem 'grape-swagger-entity'
|
||||
gem 'grape_url_validator'
|
||||
gem 'grape_logging'
|
||||
gem 'grape_devise', git: 'https://github.com/ericschultz/grape_devise.git'
|
||||
|
|
87
Gemfile.lock
87
Gemfile.lock
|
@ -23,6 +23,24 @@ GIT
|
|||
multi_json (~> 1.0)
|
||||
stripe (>= 1.31.0, <= 1.58.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/ericschultz/grape_devise.git
|
||||
revision: f1cdf2576476f0f9bf0b4f5c3e7cf07295933871
|
||||
specs:
|
||||
grape_devise (0.1.1)
|
||||
devise (>= 2.2.8, < 4)
|
||||
grape (> 0.7)
|
||||
rails (> 3.2, < 5)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/ruby-grape/grape-entity.git
|
||||
revision: 0e04aa561373b510c2486282979085eaef2ae663
|
||||
ref: 0e04aa561373b510c2486282979085eaef2ae663
|
||||
specs:
|
||||
grape-entity (0.7.1)
|
||||
activesupport (>= 3.0.0)
|
||||
multi_json (>= 1.3.2)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
|
@ -71,7 +89,11 @@ GEM
|
|||
mail (> 2.2.5)
|
||||
mime-types
|
||||
xml-simple
|
||||
bcrypt (3.1.10)
|
||||
axiom-types (0.1.1)
|
||||
descendants_tracker (~> 0.0.4)
|
||||
ice_nine (~> 0.11.0)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
bcrypt (3.1.11)
|
||||
binding_of_caller (0.7.2)
|
||||
debug_inspector (>= 0.0.1)
|
||||
bootsnap (1.1.7)
|
||||
|
@ -95,6 +117,8 @@ GEM
|
|||
simplecov
|
||||
url
|
||||
coderay (1.1.2)
|
||||
coercible (1.0.0)
|
||||
descendants_tracker (~> 0.0.1)
|
||||
colorize (0.8.1)
|
||||
concurrent-ruby (1.0.5)
|
||||
config (1.7.0)
|
||||
|
@ -115,7 +139,7 @@ GEM
|
|||
database_cleaner (1.6.1)
|
||||
debase (0.2.2)
|
||||
debase-ruby_core_source (>= 0.10.2)
|
||||
debase-ruby_core_source (0.10.2)
|
||||
debase-ruby_core_source (0.10.3)
|
||||
debug_inspector (0.0.2)
|
||||
deep_merge (1.2.1)
|
||||
delayed_job (4.1.2)
|
||||
|
@ -123,7 +147,9 @@ GEM
|
|||
delayed_job_active_record (4.1.1)
|
||||
activerecord (>= 3.0, < 5.1)
|
||||
delayed_job (>= 3.0, < 5)
|
||||
devise (3.4.1)
|
||||
descendants_tracker (0.0.4)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
devise (3.5.10)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 3.2.6, < 5)
|
||||
|
@ -166,6 +192,7 @@ GEM
|
|||
dry-equalizer (~> 0.2)
|
||||
dry-logic (~> 0.4, >= 0.4.0)
|
||||
dry-types (~> 0.12.0)
|
||||
equalizer (0.0.11)
|
||||
erubis (2.7.0)
|
||||
execjs (2.5.2)
|
||||
factory_bot (4.8.2)
|
||||
|
@ -190,6 +217,23 @@ GEM
|
|||
plissken
|
||||
geocoder (1.2.11)
|
||||
get_process_mem (0.2.1)
|
||||
grape (1.0.3)
|
||||
activesupport
|
||||
builder
|
||||
mustermann-grape (~> 1.0.0)
|
||||
rack (>= 1.3.0)
|
||||
rack-accept
|
||||
virtus (>= 1.0.0)
|
||||
grape-swagger (0.28.0)
|
||||
grape (>= 0.16.2)
|
||||
grape-swagger-entity (0.2.3)
|
||||
grape-entity (>= 0.5.0)
|
||||
grape-swagger (>= 0.20.4)
|
||||
grape_logging (1.8.0)
|
||||
grape
|
||||
rack
|
||||
grape_url_validator (1.0.0)
|
||||
grape (>= 0.12.0)
|
||||
hamster (3.0.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
hashie (3.4.1)
|
||||
|
@ -201,10 +245,12 @@ GEM
|
|||
httparty (0.13.3)
|
||||
json (~> 1.8)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (0.8.6)
|
||||
i18n (0.9.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n-js (3.0.2)
|
||||
i18n (~> 0.6, >= 0.6.6)
|
||||
i18n_data (0.8.0)
|
||||
ice_nine (0.11.2)
|
||||
inflecto (0.0.2)
|
||||
journey (1.0.4)
|
||||
json (1.8.6)
|
||||
|
@ -226,9 +272,12 @@ GEM
|
|||
money (6.10.0)
|
||||
i18n (>= 0.6.4, < 1.0)
|
||||
msgpack (1.2.0)
|
||||
multi_json (1.12.1)
|
||||
multi_json (1.13.1)
|
||||
multi_xml (0.5.5)
|
||||
multipart-post (2.0.0)
|
||||
mustermann (1.0.2)
|
||||
mustermann-grape (1.0.0)
|
||||
mustermann (~> 1.0.0)
|
||||
nearest_time_zone (0.0.4)
|
||||
andand
|
||||
kdtree
|
||||
|
@ -258,9 +307,11 @@ GEM
|
|||
rabl (0.11.6)
|
||||
activesupport (>= 2.3.14)
|
||||
rack (1.4.7)
|
||||
rack-accept (0.4.5)
|
||||
rack (>= 0.4)
|
||||
rack-attack (4.2.0)
|
||||
rack
|
||||
rack-cache (1.7.0)
|
||||
rack-cache (1.7.2)
|
||||
rack (>= 0.4)
|
||||
rack-ssl (1.3.4)
|
||||
rack
|
||||
|
@ -292,7 +343,7 @@ GEM
|
|||
rake (>= 0.8.7)
|
||||
rdoc (~> 3.4)
|
||||
thor (>= 0.14.6, < 2.0)
|
||||
rake (12.0.0)
|
||||
rake (12.3.1)
|
||||
rdoc (3.12.2)
|
||||
json (~> 1.4)
|
||||
require_all (1.3.2)
|
||||
|
@ -329,7 +380,7 @@ GEM
|
|||
rspec-mocks (~> 3.5.0)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-support (3.5.0)
|
||||
ruby-debug-ide (0.6.0)
|
||||
ruby-debug-ide (0.6.1)
|
||||
rake (>= 0.8.1)
|
||||
ruby-prof (0.15.9)
|
||||
safe_yaml (1.0.4)
|
||||
|
@ -359,7 +410,7 @@ GEM
|
|||
test-unit (3.2.7)
|
||||
power_assert
|
||||
thor (0.19.4)
|
||||
thread_safe (0.3.5)
|
||||
thread_safe (0.3.6)
|
||||
tilt (1.4.1)
|
||||
timecop (0.7.3)
|
||||
traceroute (0.5.0)
|
||||
|
@ -367,7 +418,7 @@ GEM
|
|||
treetop (1.4.15)
|
||||
polyglot
|
||||
polyglot (>= 0.3.1)
|
||||
tzinfo (0.3.53)
|
||||
tzinfo (0.3.54)
|
||||
uglifier (2.7.1)
|
||||
execjs (>= 0.3.0)
|
||||
json (>= 1.8.0)
|
||||
|
@ -377,7 +428,12 @@ GEM
|
|||
unicode_utils (1.4.0)
|
||||
url (0.3.2)
|
||||
vcr (2.9.3)
|
||||
warden (1.2.3)
|
||||
virtus (1.0.5)
|
||||
axiom-types (~> 0.1)
|
||||
coercible (~> 1.0)
|
||||
descendants_tracker (~> 0.0, >= 0.0.3)
|
||||
equalizer (~> 0.0, >= 0.0.9)
|
||||
warden (1.2.7)
|
||||
rack (>= 1.0)
|
||||
webmock (1.21.0)
|
||||
addressable (>= 2.3.6)
|
||||
|
@ -418,6 +474,13 @@ DEPENDENCIES
|
|||
foreman
|
||||
fullcontact
|
||||
geocoder
|
||||
grape
|
||||
grape-entity!
|
||||
grape-swagger
|
||||
grape-swagger-entity
|
||||
grape_devise!
|
||||
grape_logging
|
||||
grape_url_validator
|
||||
hamster
|
||||
heroku-deflater
|
||||
httparty
|
||||
|
@ -446,7 +509,7 @@ DEPENDENCIES
|
|||
roadie-rails
|
||||
rspec
|
||||
rspec-rails
|
||||
ruby-debug-ide (= 0.6.0)
|
||||
ruby-debug-ide
|
||||
ruby-prof (= 0.15.9)
|
||||
sass (= 3.2.19)
|
||||
sass-rails (= 3.2.6)
|
||||
|
|
5
app/api/houdini/api.rb
Normal file
5
app/api/houdini/api.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
|
||||
class Houdini::API < Grape::API
|
||||
format :json
|
||||
mount Houdini::V1::API => '/v1'
|
||||
end
|
21
app/api/houdini/v1/api.rb
Normal file
21
app/api/houdini/v1/api.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
require 'houdini/v1/validations'
|
||||
class Houdini::V1::API < Grape::API
|
||||
logger.formatter = GrapeLogging::Formatters::Rails.new
|
||||
use GrapeLogging::Middleware::RequestLogger, { logger: logger }
|
||||
content_type :json, 'application/json'
|
||||
default_format :json
|
||||
rescue_from Grape::Exceptions::ValidationErrors do |e|
|
||||
output = {errors: e}
|
||||
error! output, 400
|
||||
end
|
||||
|
||||
#include Houdini::V1::Helpers::ApplicationHelper
|
||||
mount Houdini::V1::Nonprofit => '/nonprofit'
|
||||
# Additional mounts are added via generators above this line
|
||||
# DON'T REMOVE THIS OR THE PREVIOUS LINES!!!
|
||||
uriForHost = URI.parse(Settings.cdn.url)
|
||||
add_swagger_documentation \
|
||||
host: "#{uriForHost.host}#{Settings.cdn.port ? ":#{Settings.cdn.port}" : ""}",
|
||||
schemes: [uriForHost.scheme],
|
||||
base_path: '/api/v1'
|
||||
end
|
30
app/api/houdini/v1/base_api.rb
Normal file
30
app/api/houdini/v1/base_api.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
|
||||
class Houdini::V1::BaseAPI < Grape::API
|
||||
#helpers ApplicationHelper
|
||||
# helpers do
|
||||
# def session
|
||||
# env['rack.session']
|
||||
# end
|
||||
#
|
||||
# def protect_against_forgery
|
||||
# unless verified_request?
|
||||
# error!('Unauthorized', 401)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# def verified_request?
|
||||
# !protect_against_forgery? || request.get? || request.head? ||
|
||||
# form_authenticity_token == request.headers['X-CSRF-Token'] ||
|
||||
# form_authenticity_token == request.headers['X-Csrf-Token']
|
||||
# end
|
||||
#
|
||||
# def form_authenticity_token
|
||||
# session[:_csrf_token] ||= SecureRandom.base64(32)
|
||||
# end
|
||||
#
|
||||
# def protect_against_forgery?
|
||||
# allow_forgery_protection = Rails.configuration.action_controller.allow_forgery_protection
|
||||
# allow_forgery_protection.nil? || allow_forgery_protection
|
||||
# end
|
||||
# end
|
||||
end
|
4
app/api/houdini/v1/entities/nonprofit.rb
Normal file
4
app/api/houdini/v1/entities/nonprofit.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
|
||||
class Houdini::V1::Entities::Nonprofit < Grape::Entity
|
||||
expose :id
|
||||
end
|
5
app/api/houdini/v1/entities/validation_error.rb
Normal file
5
app/api/houdini/v1/entities/validation_error.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
|
||||
class Houdini::V1::Entities::ValidationError < Grape::Entity
|
||||
expose :params, documentation: {type: 'String', desc: 'Params where the following had an error.', is_array: true}
|
||||
expose :messages, documentation: {type:'String', desc: 'The validation messages for the params', is_array: true}
|
||||
end
|
4
app/api/houdini/v1/entities/validation_errors.rb
Normal file
4
app/api/houdini/v1/entities/validation_errors.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
|
||||
class Houdini::V1::Entities::ValidationErrors < Grape::Entity
|
||||
expose :errors, documentation: {type: ValidationError, desc: 'errors', is_array:true}
|
||||
end
|
45
app/api/houdini/v1/helpers/application_helper.rb
Normal file
45
app/api/houdini/v1/helpers/application_helper.rb
Normal file
|
@ -0,0 +1,45 @@
|
|||
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
|
||||
module Houdini::V1::Helpers::ApplicationHelper
|
||||
extend Grape::API::Helpers
|
||||
|
||||
|
||||
def session
|
||||
env['rack.session']
|
||||
end
|
||||
|
||||
def protect_against_forgery
|
||||
unless verified_request?
|
||||
error!('Unauthorized', 401)
|
||||
end
|
||||
end
|
||||
|
||||
def verified_request?
|
||||
!protect_against_forgery? || request.get? || request.head? ||
|
||||
form_authenticity_token == request.headers['X-CSRF-Token'] ||
|
||||
form_authenticity_token == request.headers['X-Csrf-Token']
|
||||
end
|
||||
|
||||
def form_authenticity_token
|
||||
session[:_csrf_token] ||= SecureRandom.base64(32)
|
||||
end
|
||||
|
||||
def protect_against_forgery?
|
||||
allow_forgery_protection = Rails.configuration.action_controller.allow_forgery_protection
|
||||
allow_forgery_protection.nil? || allow_forgery_protection
|
||||
end
|
||||
|
||||
|
||||
# def rescue_ar_invalid( *class_to_hash)
|
||||
# rescue_with ActiveRecord::RecordInvalid do |error|
|
||||
# output = []
|
||||
# error.record.errors do |attr,message|
|
||||
# output.push({params: "#{class_to_hash[error.record.class]}['#{attr}']",
|
||||
# message: message})
|
||||
# end
|
||||
# raise Grape::Exceptions::ValidationErrors.new(output)
|
||||
#
|
||||
# end
|
||||
# end
|
||||
|
||||
end
|
||||
|
19
app/api/houdini/v1/helpers/rescue_helper.rb
Normal file
19
app/api/houdini/v1/helpers/rescue_helper.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
module Houdini::V1::Helpers::RescueHelper
|
||||
require 'active_support/concern'
|
||||
|
||||
extend ActiveSupport::Concern
|
||||
include Grape::DSL::Configuration
|
||||
module ClassMethods
|
||||
def rescue_ar_invalid( *class_to_hash)
|
||||
rescue_with ActiveRecord::RecordInvalid do |error|
|
||||
output = []
|
||||
error.record.errors do |attr,message|
|
||||
output.push({params: "#{class_to_hash[error.record.class]}['#{attr}']",
|
||||
message: message})
|
||||
end
|
||||
raise Grape::Exceptions::ValidationErrors.new(output)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
113
app/api/houdini/v1/nonprofit.rb
Normal file
113
app/api/houdini/v1/nonprofit.rb
Normal file
|
@ -0,0 +1,113 @@
|
|||
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
|
||||
class Houdini::V1::Nonprofit < Houdini::V1::BaseAPI
|
||||
helpers Houdini::V1::Helpers::ApplicationHelper, Houdini::V1::Helpers::RescueHelper
|
||||
|
||||
before do
|
||||
protect_against_forgery
|
||||
end
|
||||
|
||||
desc 'Return a nonprofit.' do
|
||||
success Houdini::V1::Entities::Nonprofit
|
||||
end
|
||||
params do
|
||||
requires :id, type: Integer, desc: 'Status id.'
|
||||
end
|
||||
route_param :id do
|
||||
get do
|
||||
np = Nonprofit.find(params[:id])
|
||||
present np, as: Houdini::V1::Entities::Nonprofit
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Register a nonprofit' do
|
||||
success Houdini::V1::Entities::Nonprofit
|
||||
|
||||
#this needs to be a validation an array
|
||||
failure [{code:400, message:'Validation Errors', model: Houdini::V1::Entities::ValidationErrors}]
|
||||
end
|
||||
|
||||
params do
|
||||
|
||||
requires :nonprofit, type: Hash do
|
||||
requires :name, type:String, desc: 'Organization Name', allow_blank: false, documentation: { param_type: 'body' }
|
||||
optional :url, type:String, desc: 'Organization website URL', allow_blank:true, regexp: URI::regexp, documentation: { param_type: 'body' }
|
||||
requires :zip_code, type:String, allow_blank: false, desc: "Organization Address ZIP Code", documentation: { param_type: 'body' }
|
||||
requires :state_code, type:String, allow_blank: false, desc: "Organization Address State Code", documentation: { param_type: 'body' }
|
||||
requires :city, type:String, allow_blank: false, desc: "Organization Address City", documentation: { param_type: 'body' }
|
||||
optional :email, type:String, desc: 'Organization email (public)', regexp: Email::Regex, documentation: { param_type: 'body' }
|
||||
optional :phone, type:String, desc: 'Organization phone (public)', documentation: { param_type: 'body' }
|
||||
end
|
||||
|
||||
requires :user, type: Hash do
|
||||
requires :name, type:String, desc: 'Full name', allow_blank:false, documentation: { param_type: 'body' }
|
||||
requires :email, type:String, desc: 'Username', allow_blank: false, documentation: { param_type: 'body' }
|
||||
requires :password, type:String, desc: 'Password', allow_blank: false, is_equal_to: :password_confirmation, documentation: { param_type: 'body' }
|
||||
requires :password_confirmation, type:String, desc: 'Password confirmation', allow_blank: false, documentation: { param_type: 'body' }
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
post do
|
||||
np = nil
|
||||
u = nil
|
||||
Qx.transaction do
|
||||
begin
|
||||
np = Nonprofit.new(OnboardAccounts.set_nonprofit_defaults(params[:nonprofit]))
|
||||
|
||||
begin
|
||||
np.save!
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
if (e.record.errors[:slug])
|
||||
begin
|
||||
slug = SlugNonprofitNamingAlgorithm.new(np.state_code_slug, np.city_slug).create_copy_name(np.slug)
|
||||
np.slug = slug
|
||||
np.save!
|
||||
rescue UnableToCreateNameCopyError
|
||||
raise Grape::Exceptions::ValidationErrors.new(errors:[Grape::Exceptions::Validation.new(
|
||||
|
||||
params: ["nonprofit[name]"],
|
||||
message: "has an invalid slug. Contact support for help."
|
||||
)])
|
||||
end
|
||||
else
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
u = User.new(params[:user])
|
||||
u.save!
|
||||
|
||||
role = u.roles.build(host: np, name: 'nonprofit_admin')
|
||||
role.save!
|
||||
|
||||
billing_plan = BillingPlan.find(Settings.default_bp.id)
|
||||
b_sub = np.build_billing_subscription(billing_plan: billing_plan, status: 'active')
|
||||
b_sub.save!
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
class_to_name = {Nonprofit => 'nonprofit', User => 'user'}
|
||||
if class_to_name[e.record.class]
|
||||
errors = e.record.errors.keys.map {|k|
|
||||
|
||||
errors = e.record.errors[k].uniq
|
||||
errors.map{|error| Grape::Exceptions::Validation.new(
|
||||
|
||||
params: ["#{class_to_name[e.record.class]}[#{k.to_s}]"],
|
||||
message: error
|
||||
|
||||
)}
|
||||
}
|
||||
|
||||
raise Grape::Exceptions::ValidationErrors.new(errors:errors.flatten)
|
||||
else
|
||||
raise e
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
#onboard callback
|
||||
present np, with: Houdini::V1::Entities::Nonprofit
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
2
app/api/houdini/v1/validations.rb
Normal file
2
app/api/houdini/v1/validations.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
|
||||
require 'houdini/v1/validators/is_equal_to'
|
8
app/api/houdini/v1/validators/is_equal_to.rb
Normal file
8
app/api/houdini/v1/validators/is_equal_to.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
|
||||
class Houdini::V1::Validators::IsEqualTo < Grape::Validations::Base
|
||||
def validate_param!(attr_name, params)
|
||||
if params[attr_name] != params[@option]
|
||||
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: 'MESSAGE'
|
||||
end
|
||||
end
|
||||
end
|
2
app/assets/javascripts/onboard.js
Normal file
2
app/assets/javascripts/onboard.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
// Place all the behaviors and hooks related to the matching controller here.
|
||||
// All this logic will automatically be available in application.js.
|
6
app/controllers/onboard_controller.rb
Normal file
6
app/controllers/onboard_controller.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
class OnboardController < ApplicationController
|
||||
layout 'layouts/apified'
|
||||
def index
|
||||
|
||||
end
|
||||
end
|
|
@ -1,13 +1,13 @@
|
|||
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
|
||||
class Users::SessionsController < Devise::SessionsController
|
||||
|
||||
def create
|
||||
def create
|
||||
respond_to do |format|
|
||||
format.html { super }
|
||||
format.json {
|
||||
warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
|
||||
render :status => 200, :json => { :status => "Success" }
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
2
app/helpers/onboard_helper.rb
Normal file
2
app/helpers/onboard_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module OnboardHelper
|
||||
end
|
|
@ -82,7 +82,7 @@ class Nonprofit < ActiveRecord::Base
|
|||
validates :city, presence: true
|
||||
validates :state_code, presence: true
|
||||
validates :email, format: { with: Email::Regex }, allow_blank: true
|
||||
validates_uniqueness_of :slug, scope: [:city, :state_code]
|
||||
validates_uniqueness_of :slug, scope: [:city_slug, :state_code_slug]
|
||||
validates_presence_of :slug
|
||||
|
||||
scope :vetted, -> {where(vetted: true)}
|
||||
|
@ -140,9 +140,16 @@ class Nonprofit < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def set_slugs
|
||||
self.slug = Format::Url.convert_to_slug self.name
|
||||
self.city_slug = Format::Url.convert_to_slug self.city
|
||||
self.state_code_slug = Format::Url.convert_to_slug self.state_code
|
||||
unless (self.slug)
|
||||
self.slug = Format::Url.convert_to_slug self.name
|
||||
end
|
||||
unless (self.city_slug)
|
||||
self.city_slug = Format::Url.convert_to_slug self.city
|
||||
end
|
||||
|
||||
unless (self.state_code_slug)
|
||||
self.state_code_slug = Format::Url.convert_to_slug self.state_code
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
|
|
27
app/views/layouts/apified.html.erb
Normal file
27
app/views/layouts/apified.html.erb
Normal file
|
@ -0,0 +1,27 @@
|
|||
<%- # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -%>
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
|
||||
|
||||
<%= IncludeAsset.js '/client/js/i18n.js' %>
|
||||
<script>
|
||||
I18n.defaultLocale = "<%= I18n.default_locale %>"
|
||||
I18n.locale = "<%= I18n.locale %>"
|
||||
window._csrf = "<%= form_authenticity_token %>"
|
||||
</script>
|
||||
|
||||
<%= IncludeAsset.js 'app/react.js' %>
|
||||
<%= IncludeAsset.js 'app/react-dom.js' %>
|
||||
<%= IncludeAsset.js 'app/vendor.js' %>
|
||||
<%= yield :javascripts %>
|
||||
<%= render 'layouts/stylesheets' %>
|
||||
<%= IncludeAsset.css 'client/css/global/page.css' %>
|
||||
<%= IncludeAsset.css 'client/css/bootstrap.css' %>
|
||||
</head>
|
||||
<body>
|
||||
<%= yield %>
|
||||
|
||||
</body>
|
||||
</html>
|
7
app/views/onboard/index.html.erb
Normal file
7
app/views/onboard/index.html.erb
Normal file
|
@ -0,0 +1,7 @@
|
|||
<% content_for :javascripts do %>
|
||||
<%= IncludeAsset.js 'app/registration_pagex.js' %>
|
||||
<% end %>
|
||||
|
||||
<div id="outlet"></div>
|
||||
|
||||
<script>LoadReactPage(document.getElementById('outlet'))</script>
|
|
@ -1,10 +1,10 @@
|
|||
/* License: LGPL-3.0-or-later */
|
||||
[class*="container"] {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
/*[class*="container"] {*/
|
||||
/*margin-left: auto;*/
|
||||
/*margin-right: auto;*/
|
||||
/*}*/
|
||||
|
||||
.container { max-width: 60rem; }
|
||||
.container--medium { max-width: 50rem; }
|
||||
.container--narrow { max-width: 40rem; }
|
||||
/*.container { max-width: 60rem; }*/
|
||||
/*.container--medium { max-width: 50rem; }*/
|
||||
/*.container--narrow { max-width: 40rem; }*/
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
@import 'commons.css'; /* npm */
|
||||
@import 'colors.css'; /* contains variables */
|
||||
@import 'shadows.css'; /* contains variables */
|
||||
@import 'typography.css';
|
||||
/*@import 'typography.css';*/
|
||||
@import 'icons.css';
|
||||
@import 'containers.css';
|
||||
@import 'buttons.css';
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
/* License: LGPL-3.0-or-later */
|
||||
@font-face {
|
||||
font-family: 'OpenSans';
|
||||
src: url('/fonts/OpenSans/OpenSans-Regular.ttf') format('truetype');
|
||||
font-family: 'Open Sans';
|
||||
src: url('/fonts/Open_Sans/opensans-regular-webfont.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'OpenSans';
|
||||
src: url('/fonts/OpenSans/OpenSans-Semibold.ttf') format('truetype');
|
||||
font-family: 'Open Sans';
|
||||
src: url('/fonts/Open_Sans/OpenSans-Semibold.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'OpenSans';
|
||||
src: url('/fonts/OpenSans/OpenSans-Bold.ttf') format('truetype');
|
||||
font-family: 'Open Sans';
|
||||
src: url('/fonts/Open_Sans/opensans-bold-webfont.ttf') format('truetype');
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family:
|
||||
font-family:
|
||||
OpenSans,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
Segoe UI,
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
OpenSans,
|
||||
sans-serif;
|
||||
line-height: 1.5;
|
||||
color: var(--black);
|
||||
|
|
|
@ -16,6 +16,10 @@ module Commitchange
|
|||
# Custom directories with classes and modules you want to be autoloadable.
|
||||
# config.autoload_paths += %W(#{config.root}/extras)
|
||||
config.autoload_paths += Dir["#{config.root}/lib/**/"]
|
||||
|
||||
config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
|
||||
config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
|
||||
|
||||
# Only load the plugins named here, in the order given (default is alphabetical).
|
||||
# :all can be used as a placeholder for all plugins not explicitly named.
|
||||
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
|
||||
|
|
|
@ -8,13 +8,14 @@ Encoding.default_internal = Encoding::UTF_8
|
|||
require 'dotenv'
|
||||
Dotenv.load ".env"
|
||||
@env = Rails.env || 'development'
|
||||
puts "config files .env .env.#{@env} ./config/settings.#{@env}.yml#{ @env != 'test' ? " ./config/#{ENV.fetch('ORG_NAME')}.yml": " "} #{ @env != 'test' ? " ./config/#{ENV.fetch('ORG_NAME')}.#{@env}.yml": " "} #{ @env == 'test' ? "./config/settings.test.yml" : ""}"
|
||||
@org_name = ENV['ORG_NAME'] || 'default_organization'
|
||||
puts "config files .env .env.#{@env} ./config/settings.#{@env}.yml#{ @env != 'test' ? " ./config/#{@org_name}.yml": " "} #{ @env != 'test' ? " ./config/#{@org_name}.#{@env}.yml": " "} #{ @env == 'test' ? "./config/settings.test.yml" : ""}"
|
||||
Dotenv.load ".env.#{@env}" if File.file?(".env.#{@env}")
|
||||
if Rails.env == 'test'
|
||||
Settings.add_source!("./config/settings.test.yml")
|
||||
else
|
||||
Settings.add_source!("./config/#{ENV.fetch('ORG_NAME')}.yml")
|
||||
Settings.add_source!("./config/#{ENV.fetch('ORG_NAME')}.#{Rails.env}.yml")
|
||||
Settings.add_source!("./config/#{@org_name}.yml")
|
||||
Settings.add_source!("./config/#{@org_name}.#{Rails.env}.yml")
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -39,4 +39,6 @@ Commitchange::Application.configure do
|
|||
config.assets.debug = true
|
||||
|
||||
config.log_level = :debug
|
||||
|
||||
config.action_controller.allow_forgery_protection = false
|
||||
end
|
||||
|
|
11
config/initializers/reload_api.rb
Normal file
11
config/initializers/reload_api.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
if Rails.env.development?
|
||||
ActiveSupport::Dependencies.explicitly_unloadable_constants << 'Houdini::V1'
|
||||
|
||||
api_files = Dir[Rails.root.join('app', 'api', '**', '*.rb')]
|
||||
api_reloader = ActiveSupport::FileUpdateChecker.new(api_files) do
|
||||
Rails.application.reload_routes!
|
||||
end
|
||||
ActionDispatch::Callbacks.to_prepare do
|
||||
api_reloader.execute_if_updated
|
||||
end
|
||||
end
|
|
@ -139,3 +139,31 @@ en:
|
|||
twitter: "Tweet"
|
||||
twitter_message: "Join me in supporting"
|
||||
finish: "Finish"
|
||||
registration:
|
||||
get_started:
|
||||
header: "Get started"
|
||||
description: "Let's get started with Houdini. To begin, fill out your initial info nonprofit and user info."
|
||||
wizard:
|
||||
tabs:
|
||||
nonprofit: "Nonprofit"
|
||||
contact: "Contact"
|
||||
nonprofit:
|
||||
name: "Organization Name"
|
||||
website: "Website URL"
|
||||
email: "Org Email (public)"
|
||||
phone: "Org Phone (public)"
|
||||
city: "City"
|
||||
state: "State"
|
||||
zip: "Zip Code"
|
||||
contact:
|
||||
name: "Your Name"
|
||||
email: "Your Email (used for login)"
|
||||
password: "New Password"
|
||||
password_confirmation: "Retype Password"
|
||||
phone: "Your Phone (for account recovery)"
|
||||
save_and_finish: "Save & Finish"
|
||||
next: "Next"
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
|
||||
Commitchange::Application.routes.draw do
|
||||
mount Houdini::API => '/api'
|
||||
|
||||
if Rails.env == 'development'
|
||||
get '/button_debug/embedded' => 'button_debug#embedded'
|
||||
get '/button_debug/button' => 'button_debug#button'
|
||||
get '/button_debug/embedded/:id' => 'button_debug#embedded'
|
||||
get '/button_debug/button/:id' => 'button_debug#button'
|
||||
end
|
||||
end
|
||||
get 'onboard' => 'onboard#index'
|
||||
|
||||
resources(:emails, {only: [:create]})
|
||||
resources(:settings, {only: [:index]})
|
||||
resources(:pricing, {only: [:index]})
|
||||
|
|
|
@ -48,7 +48,7 @@ page_editor:
|
|||
editor: 'quill'
|
||||
|
||||
language: 'en'
|
||||
available_locales: ['en']
|
||||
available_locales: ['en', 'de']
|
||||
|
||||
intntl:
|
||||
currencies: ["usd"]
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
// License: LGPL-3.0-or-later
|
||||
|
||||
// require a root component here. This will be treated as the root of a webpack package
|
||||
|
||||
import "../src/components/registration_page/registration_page"
|
16
javascripts/app/registration_page.tsx
Normal file
16
javascripts/app/registration_page.tsx
Normal file
|
@ -0,0 +1,16 @@
|
|||
// License: LGPL-3.0-or-later
|
||||
|
||||
// require a root component here. This will be treated as the root of a webpack package
|
||||
import Root from "../src/components/common/Root"
|
||||
import RegistrationPage from "../src/components/registration_page/RegistrationPage"
|
||||
|
||||
import * as ReactDOM from 'react-dom'
|
||||
import * as React from 'react'
|
||||
|
||||
function LoadReactPage(element:HTMLElement) {
|
||||
ReactDOM.render(<Root><RegistrationPage/></Root>, element)
|
||||
}
|
||||
|
||||
|
||||
(window as any).LoadReactPage = LoadReactPage
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// License: LGPL-3.0-or-later
|
||||
import * as React from 'react';
|
||||
import 'jest';
|
||||
import {shallow} from 'enzyme'
|
||||
import toJson from 'enzyme-to-json'
|
||||
import LabeledFieldComponent from './LabeledFieldComponent'
|
||||
|
||||
describe('LabeledFieldComponent', () => {
|
||||
test('In Error with Children', () => {
|
||||
let result = shallow(<LabeledFieldComponent inputId={"ID"} labelText={"Our Label"} inError={true}
|
||||
error={"errorMessage"}>
|
||||
<hr/>
|
||||
</LabeledFieldComponent>)
|
||||
expect(toJson(result)).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('has error checked but no message so not really in error', () => {
|
||||
let result = shallow(<LabeledFieldComponent inputId={"ID"} labelText={"Our Label"} inError={true} error={null}>
|
||||
<hr/>
|
||||
</LabeledFieldComponent>)
|
||||
expect(toJson(result)).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('no error', () => {
|
||||
let result = shallow(<LabeledFieldComponent inputId={"ID"} labelText={"Our Label"} inError={false}>
|
||||
<hr/>
|
||||
</LabeledFieldComponent>)
|
||||
expect(toJson(result)).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('add extra classNames', () => {
|
||||
let result = shallow(<LabeledFieldComponent inputId={"ID"} labelText={"Our Label"} inError={false}
|
||||
className={"a_class another_class"}>
|
||||
<hr/>
|
||||
</LabeledFieldComponent>)
|
||||
expect(toJson(result)).toMatchSnapshot()
|
||||
})
|
||||
})
|
31
javascripts/src/components/common/LabeledFieldComponent.tsx
Normal file
31
javascripts/src/components/common/LabeledFieldComponent.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
// License: LGPL-3.0-or-later
|
||||
import * as React from 'react';
|
||||
import StandardFieldComponent from "./StandardFieldComponent";
|
||||
import { observer } from 'mobx-react';
|
||||
import {Field} from "../../../../types/mobx-react-form";
|
||||
import {injectIntl, InjectedIntl} from 'react-intl';
|
||||
|
||||
|
||||
export interface LabeledFieldComponentProps
|
||||
{
|
||||
inputId: string
|
||||
labelText: string
|
||||
inError:boolean
|
||||
error?:string
|
||||
className?:string
|
||||
}
|
||||
|
||||
@observer
|
||||
export default class LabeledFieldComponent extends React.Component<LabeledFieldComponentProps, {}> {
|
||||
render() {
|
||||
let className = this.props.className || ""
|
||||
let inError = this.props.inError && this.props.error !== null && this.props.error !== "";
|
||||
className += " form-group"
|
||||
className += inError ? " has-error" : ""
|
||||
return <fieldset className={className}><label htmlFor={this.props.inputId} className="control-label">{this.props.labelText}</label>
|
||||
<StandardFieldComponent inError={inError} error={this.props.error} >{this.props.children}</StandardFieldComponent>
|
||||
</fieldset>;
|
||||
}
|
||||
}
|
||||
|
||||
|
44
javascripts/src/components/common/Root.tsx
Normal file
44
javascripts/src/components/common/Root.tsx
Normal file
|
@ -0,0 +1,44 @@
|
|||
// License: LGPL-3.0-or-later
|
||||
import * as React from 'react';
|
||||
import { observer, Provider } from 'mobx-react';
|
||||
import { IntlProvider, addLocaleData} from 'react-intl';
|
||||
const enLocaleData = require('react-intl/locale-data/en');
|
||||
const deLocaleData = require('react-intl/locale-data/de');
|
||||
const I18n = require('i18n')
|
||||
import {convert} from 'dotize'
|
||||
import {ApiManager} from "../../lib/api_manager";
|
||||
import {APIS} from "../../../api";
|
||||
import {CSRFInterceptor} from "../../lib/csrf_interceptor";
|
||||
|
||||
import * as CustomAPIS from "../../lib/apis"
|
||||
|
||||
addLocaleData([...enLocaleData, ...deLocaleData])
|
||||
|
||||
interface RootProps
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
@observer
|
||||
export default class Root extends React.Component<RootProps, {}> {
|
||||
|
||||
apiManager: ApiManager
|
||||
|
||||
render() {
|
||||
if (!this.apiManager){
|
||||
this.apiManager = new ApiManager(APIS.concat(CustomAPIS.APIS as Array<any>), CSRFInterceptor)
|
||||
}
|
||||
|
||||
return <IntlProvider locale={I18n.locale} defaultLocale={I18n.defaultLocale} messages={convert(I18n.translations[I18n.locale])}>
|
||||
<Provider ApiManager={this.apiManager}>
|
||||
{this.props.children}
|
||||
</Provider>
|
||||
</IntlProvider>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// License: LGPL-3.0-or-later
|
||||
import * as React from 'react';
|
||||
import 'jest';
|
||||
import {shallow,render} from 'enzyme'
|
||||
const TestRenderer = require('react-test-renderer')
|
||||
import StandardFieldComponent from './StandardFieldComponent'
|
||||
import toJson from 'enzyme-to-json';
|
||||
|
||||
describe('StandardFieldComponent', () => {
|
||||
test('works with no children', () => {
|
||||
var field = shallow(<StandardFieldComponent inError={false} />)
|
||||
|
||||
|
||||
expect(toJson(field)).toMatchSnapshot()
|
||||
})
|
||||
test('works with a child', () => {
|
||||
var field = shallow(<StandardFieldComponent inError={false}><input/></StandardFieldComponent>);
|
||||
|
||||
expect(toJson(field)).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('sets error message properly', () => {
|
||||
var field = shallow(<StandardFieldComponent inError={true} error={"Something more"}><input/></StandardFieldComponent>);
|
||||
|
||||
expect(toJson(field)).toMatchSnapshot()
|
||||
})
|
||||
})
|
35
javascripts/src/components/common/StandardFieldComponent.tsx
Normal file
35
javascripts/src/components/common/StandardFieldComponent.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
// License: LGPL-3.0-or-later
|
||||
import * as React from 'react';
|
||||
|
||||
export interface StandardFieldComponentProps
|
||||
{
|
||||
inError:boolean
|
||||
error?:string
|
||||
children?:React.ReactNode
|
||||
[additional_properties:string]: any
|
||||
}
|
||||
|
||||
export default class StandardFieldComponent extends React.Component<StandardFieldComponentProps, {}> {
|
||||
constructor(props:StandardFieldComponentProps){
|
||||
super(props)
|
||||
}
|
||||
renderChildren(){
|
||||
return React.Children.map(this.props.children, child => {
|
||||
return React.cloneElement(child as React.ReactElement<any>, {
|
||||
className: "form-control"
|
||||
})
|
||||
})
|
||||
}
|
||||
render() {
|
||||
let errorMessage = this.props.inError ? this.props.error : undefined
|
||||
let errorDiv = this.props.inError? <div className="help-block" role="alert">{errorMessage}</div> : ""
|
||||
|
||||
return <div>
|
||||
{this.renderChildren()}
|
||||
{errorDiv}
|
||||
</div>
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LabeledFieldComponent In Error with Children 1`] = `
|
||||
<fieldset
|
||||
className=" form-group has-error"
|
||||
>
|
||||
<label
|
||||
className="control-label"
|
||||
htmlFor="ID"
|
||||
>
|
||||
Our Label
|
||||
</label>
|
||||
<StandardFieldComponent
|
||||
error="errorMessage"
|
||||
inError={true}
|
||||
>
|
||||
<hr />
|
||||
</StandardFieldComponent>
|
||||
</fieldset>
|
||||
`;
|
||||
|
||||
exports[`LabeledFieldComponent add extra classNames 1`] = `
|
||||
<fieldset
|
||||
className="a_class another_class form-group"
|
||||
>
|
||||
<label
|
||||
className="control-label"
|
||||
htmlFor="ID"
|
||||
>
|
||||
Our Label
|
||||
</label>
|
||||
<StandardFieldComponent
|
||||
inError={false}
|
||||
>
|
||||
<hr />
|
||||
</StandardFieldComponent>
|
||||
</fieldset>
|
||||
`;
|
||||
|
||||
exports[`LabeledFieldComponent has error checked but no message so not really in error 1`] = `
|
||||
<fieldset
|
||||
className=" form-group"
|
||||
>
|
||||
<label
|
||||
className="control-label"
|
||||
htmlFor="ID"
|
||||
>
|
||||
Our Label
|
||||
</label>
|
||||
<StandardFieldComponent
|
||||
error={null}
|
||||
inError={false}
|
||||
>
|
||||
<hr />
|
||||
</StandardFieldComponent>
|
||||
</fieldset>
|
||||
`;
|
||||
|
||||
exports[`LabeledFieldComponent no error 1`] = `
|
||||
<fieldset
|
||||
className=" form-group"
|
||||
>
|
||||
<label
|
||||
className="control-label"
|
||||
htmlFor="ID"
|
||||
>
|
||||
Our Label
|
||||
</label>
|
||||
<StandardFieldComponent
|
||||
inError={false}
|
||||
>
|
||||
<hr />
|
||||
</StandardFieldComponent>
|
||||
</fieldset>
|
||||
`;
|
|
@ -0,0 +1,27 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`StandardFieldComponent sets error message properly 1`] = `
|
||||
<div>
|
||||
<input
|
||||
className="form-control"
|
||||
key=".0"
|
||||
/>
|
||||
<div
|
||||
className="help-block"
|
||||
role="alert"
|
||||
>
|
||||
Something more
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`StandardFieldComponent works with a child 1`] = `
|
||||
<div>
|
||||
<input
|
||||
className="form-control"
|
||||
key=".0"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`StandardFieldComponent works with no children 1`] = `<div />`;
|
16
javascripts/src/components/common/fields.tsx
Normal file
16
javascripts/src/components/common/fields.tsx
Normal file
|
@ -0,0 +1,16 @@
|
|||
// License: LGPL-3.0-or-later
|
||||
import * as React from 'react';
|
||||
import {observer} from "mobx-react";
|
||||
import * as _ from 'lodash'
|
||||
import {Field} from "../../../../types/mobx-react-form";
|
||||
import LabeledFieldComponent from "./LabeledFieldComponent";
|
||||
import {injectIntl, InjectedIntl} from 'react-intl';
|
||||
|
||||
|
||||
export const BasicField = injectIntl(observer((props:{field:Field, intl?:InjectedIntl, wrapperClassName?:string}) =>{
|
||||
return <LabeledFieldComponent
|
||||
inputId={props.field.id} labelText={props.field.label} inError={props.field.hasError} error={props.field.error} className={props.wrapperClassName} >
|
||||
|
||||
<input {...props.field.bind()} className="form-control"/>
|
||||
</LabeledFieldComponent>
|
||||
}))
|
38
javascripts/src/components/common/layout.tsx
Normal file
38
javascripts/src/components/common/layout.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
// License: LGPL-3.0-or-later
|
||||
import * as React from 'react';
|
||||
import {observer} from "mobx-react";
|
||||
import * as _ from 'lodash'
|
||||
|
||||
export const TwoColumnFields = observer((props:{children:Array<React.ReactElement<any>>}) => {
|
||||
return <div className="clearfix">
|
||||
{
|
||||
_.take(props.children, 2).map((i:React.ReactElement<any>) => {
|
||||
let className = "col-left-6"
|
||||
if (_.last(props.children) !== i){
|
||||
className += " u-paddingRight--10"
|
||||
}
|
||||
if (i.props['className']){
|
||||
className += i.props['className']
|
||||
}
|
||||
|
||||
return React.cloneElement(i, {wrapperClassName: className})
|
||||
})}
|
||||
</div>
|
||||
})
|
||||
|
||||
export const ThreeColumnFields = observer((props:{children:React.ReactElement<any>[]}) => {
|
||||
return <div className="clearfix">
|
||||
{
|
||||
_.take(props.children, 3).map((i:React.ReactElement<any>) => {
|
||||
let className = "col-left-4"
|
||||
if (_.last(props.children) !== i){
|
||||
className += " u-paddingRight--10"
|
||||
}
|
||||
if (i.props['className']){
|
||||
className += i.props['className']
|
||||
}
|
||||
|
||||
return React.cloneElement(i, {wrapperClassName: className})
|
||||
})}
|
||||
</div>
|
||||
})
|
53
javascripts/src/components/common/wizard/ManagedWrapper.ts
Normal file
53
javascripts/src/components/common/wizard/ManagedWrapper.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
// License: LGPL-3.0-or-later
|
||||
import * as React from 'react'
|
||||
import * as RAT from "react-aria-tabpanel";
|
||||
import {TabManager} from "./manager";
|
||||
var PropTypes = require('prop-types');
|
||||
|
||||
var innerCreateManager = require('react-aria-tabpanel/lib/createManager');
|
||||
var specialAssign = require('react-aria-tabpanel/lib/specialAssign');
|
||||
|
||||
interface AddManagerInterface {
|
||||
manager?: TabManager
|
||||
}
|
||||
|
||||
|
||||
var checkedProps = {
|
||||
children: PropTypes.node.isRequired,
|
||||
activeTabId: PropTypes.string,
|
||||
letterNavigation: PropTypes.bool,
|
||||
onChange: PropTypes.func,
|
||||
tag: PropTypes.string,
|
||||
};
|
||||
|
||||
/**
|
||||
* Works just like the normal Wrapper but provides a tool for passing in our own TabManager
|
||||
*/
|
||||
export class ManagedWrapper extends RAT.Wrapper<AddManagerInterface>
|
||||
{
|
||||
manager: TabManager
|
||||
constructor(props:RAT.WrapperProps & AddManagerInterface){
|
||||
super(props)
|
||||
|
||||
if (props.manager)
|
||||
this.manager = this.props.manager
|
||||
}
|
||||
|
||||
componentWillMount(){
|
||||
|
||||
console.log('seomte')
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
var props = this.props;
|
||||
var elProps = {};
|
||||
specialAssign(elProps, props, checkedProps);
|
||||
return React.createElement(props.tag, elProps, props.children);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
111
javascripts/src/components/common/wizard/Wizard.spec.tsx
Normal file
111
javascripts/src/components/common/wizard/Wizard.spec.tsx
Normal file
|
@ -0,0 +1,111 @@
|
|||
// License: LGPL-3.0-or-later
|
||||
import * as React from 'react';
|
||||
import 'jest';
|
||||
import * as Component from './Wizard'
|
||||
import {WizardState, WizardTabPanelState} from "./wizard_state";
|
||||
import {Form} from "mobx-react-form";
|
||||
import {computed, observable, action} from 'mobx';
|
||||
import {Wizard} from "./Wizard";
|
||||
import {shallow} from 'enzyme';
|
||||
import {WizardPanel} from "./WizardPanel";
|
||||
import toJson from 'enzyme-to-json';
|
||||
|
||||
class MockableTabPanelState extends WizardTabPanelState
|
||||
{
|
||||
@observable
|
||||
customIsValid: boolean
|
||||
|
||||
@action.bound
|
||||
setValid(validity:boolean){
|
||||
this.customIsValid = validity;
|
||||
}
|
||||
|
||||
@computed
|
||||
get isValid():boolean {
|
||||
return this.customIsValid
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class EasyWizardState extends WizardState{
|
||||
constructor(){
|
||||
super(MockableTabPanelState)
|
||||
}
|
||||
createForm(i: any): Form {
|
||||
return new Form(i)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
describe('Wizard', () => {
|
||||
let data =
|
||||
{
|
||||
tab1: {
|
||||
tabName: "Tab1",
|
||||
label: "Label1",
|
||||
subFormDef: {extra: "nothing" }
|
||||
},
|
||||
tab2: {
|
||||
tabName: "Tab2",
|
||||
label: "Label2",
|
||||
subFormDef: {extra: "not" }
|
||||
},
|
||||
tab3: {
|
||||
tabName: "Tab3",
|
||||
label: "Label3",
|
||||
subFormDef: {extra: "no3t" }
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
|
||||
let state:EasyWizardState = null
|
||||
let tab1: MockableTabPanelState, tab2: MockableTabPanelState, tab3 : MockableTabPanelState = null
|
||||
|
||||
beforeEach(() => {
|
||||
state = new EasyWizardState()
|
||||
state.addTab(data.tab1.tabName, data.tab1.label, data.tab1.subFormDef)
|
||||
state.addTab(data.tab2.tabName, data.tab2.label, data.tab2.subFormDef)
|
||||
state.addTab(data.tab3.tabName, data.tab3.label, data.tab3.subFormDef)
|
||||
state.initialize()
|
||||
tab1 = (state.tabsByName[data.tab1.tabName] as MockableTabPanelState)
|
||||
tab1.setValid(true)
|
||||
tab2 = (state.tabsByName[data.tab2.tabName] as MockableTabPanelState)
|
||||
tab2.setValid(true)
|
||||
tab3 = (state.tabsByName[data.tab3.tabName] as MockableTabPanelState)
|
||||
})
|
||||
|
||||
function createWizard(disabledTabs:boolean) {
|
||||
return <Wizard wizardState={state} disableTabs={disabledTabs}>
|
||||
<WizardPanel tab={tab1} />
|
||||
<WizardPanel tab={tab2} />
|
||||
<WizardPanel tab={tab3} />
|
||||
</Wizard>
|
||||