diff --git a/.circleci/config.yml b/.circleci/config.yml index 7b72fb0c0..9579c23a8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ defaults: &defaults working_directory: ~/build docker: # specify the version you desire here - - image: circleci/ruby:2.7.0-node-browsers + - image: circleci/ruby:2.7.1-node-browsers # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images diff --git a/.env.example b/.env.example index b80e3577d..d4fb1ea12 100644 --- a/.env.example +++ b/.env.example @@ -73,10 +73,6 @@ RAILS_LOG_TO_STDOUT=true LOG_LEVEL=info LOG_SIZE=500 -# Credentials to access sidekiq dashboard in production -SIDEKIQ_AUTH_USERNAME= -SIDEKIQ_AUTH_PASSWORD= - ### This environment variables are only required if you are setting up social media channels #facebook FB_VERIFY_TOKEN= @@ -106,3 +102,10 @@ CHARGEBEE_WEBHOOK_PASSWORD= ## generate a new key value here : https://d3v.one/vapid-key-generator/ # VAPID_PUBLIC_KEY= # VAPID_PRIVATE_KEY= + +## Bot Customizations +USE_INBOX_AVATAR_FOR_BOT=true + +## Development Only Config +# if you want to use letter_opener for local emails +# LETTER_OPENER=true diff --git a/.rubocop.yml b/.rubocop.yml index 8d2405e3f..fac44c67a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -12,6 +12,8 @@ Layout/LineLength: Max: 150 Metrics/ClassLength: Max: 125 + Exclude: + - 'app/models/conversation.rb' RSpec/ExampleLength: Max: 25 Style/Documentation: @@ -30,6 +32,7 @@ Style/GlobalVars: Exclude: - 'config/initializers/redis.rb' - 'lib/redis/alfred.rb' + - 'lib/global_config.rb' Metrics/BlockLength: Exclude: - spec/**/* @@ -94,6 +97,8 @@ Rails/UniqueValidationWithoutIndex: Exclude: - 'app/models/channel/twitter_profile.rb' - 'app/models/webhook.rb' +RSpec/NamedSubject: + Enabled: false AllCops: Exclude: - 'bin/**/*' diff --git a/.ruby-version b/.ruby-version index 24ba9a38d..860487ca1 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.7.0 +2.7.1 diff --git a/.scss-lint.yml b/.scss-lint.yml index 9f5f4fe10..481d94c70 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -177,6 +177,8 @@ linters: allow_element_with_attribute: false allow_element_with_class: false allow_element_with_id: false + exclude: + - 'app/assets/stylesheets/administrate/components/_buttons.scss' SelectorDepth: enabled: true @@ -279,3 +281,4 @@ linters: exclude: - 'app/javascript/widget/assets/scss/_reset.scss' - 'app/javascript/widget/assets/scss/sdk.css' + - 'app/assets/stylesheets/administrate/reset/_normalize.scss' diff --git a/Gemfile b/Gemfile index e8e2fa85b..50e3e2ce8 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source 'https://rubygems.org' -ruby '2.7.0' +ruby '2.7.1' ##-- base gems for rails --## gem 'rack-cors', require: 'rack/cors' @@ -49,6 +49,8 @@ gem 'devise_token_auth' # authorization gem 'jwt' gem 'pundit' +# super admin +gem 'administrate' ##--- gems for pubsub service ---## # https://karolgalanciak.com/blog/2019/11/30/from-activerecord-callbacks-to-publish-slash-subscribe-pattern-and-event-driven-design/ @@ -62,7 +64,8 @@ gem 'facebook-messenger' gem 'telegram-bot-ruby' gem 'twilio-ruby', '~> 5.32.0' # twitty will handle subscription of twitter account events -gem 'twitty', git: 'https://github.com/chatwoot/twitty' +# gem 'twitty', git: 'https://github.com/chatwoot/twitty' +gem 'twitty' # facebook client gem 'koala' # Random name generator diff --git a/Gemfile.lock b/Gemfile.lock index f26503fa4..48e4e12a8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,3 @@ -GIT - remote: https://github.com/chatwoot/twitty - revision: af4f3e45dca55e42c64f7741a1fedfaa94d36419 - specs: - twitty (0.1.0) - oauth - GIT remote: https://github.com/sds/mock_redis revision: 16d00789f0341a3aac35126c0ffe97a596753ff9 @@ -25,85 +18,98 @@ GEM specs: action-cable-testing (0.6.1) actioncable (>= 5.0) - actioncable (6.0.2.2) - actionpack (= 6.0.2.2) + actioncable (6.0.3.1) + actionpack (= 6.0.3.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.0.2.2) - actionpack (= 6.0.2.2) - activejob (= 6.0.2.2) - activerecord (= 6.0.2.2) - activestorage (= 6.0.2.2) - activesupport (= 6.0.2.2) + actionmailbox (6.0.3.1) + actionpack (= 6.0.3.1) + activejob (= 6.0.3.1) + activerecord (= 6.0.3.1) + activestorage (= 6.0.3.1) + activesupport (= 6.0.3.1) mail (>= 2.7.1) - actionmailer (6.0.2.2) - actionpack (= 6.0.2.2) - actionview (= 6.0.2.2) - activejob (= 6.0.2.2) + actionmailer (6.0.3.1) + actionpack (= 6.0.3.1) + actionview (= 6.0.3.1) + activejob (= 6.0.3.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.0.2.2) - actionview (= 6.0.2.2) - activesupport (= 6.0.2.2) + actionpack (6.0.3.1) + actionview (= 6.0.3.1) + activesupport (= 6.0.3.1) rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.0.2.2) - actionpack (= 6.0.2.2) - activerecord (= 6.0.2.2) - activestorage (= 6.0.2.2) - activesupport (= 6.0.2.2) + actiontext (6.0.3.1) + actionpack (= 6.0.3.1) + activerecord (= 6.0.3.1) + activestorage (= 6.0.3.1) + activesupport (= 6.0.3.1) nokogiri (>= 1.8.5) - actionview (6.0.2.2) - activesupport (= 6.0.2.2) + actionview (6.0.3.1) + activesupport (= 6.0.3.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.0.2.2) - activesupport (= 6.0.2.2) + activejob (6.0.3.1) + activesupport (= 6.0.3.1) globalid (>= 0.3.6) - activemodel (6.0.2.2) - activesupport (= 6.0.2.2) - activerecord (6.0.2.2) - activemodel (= 6.0.2.2) - activesupport (= 6.0.2.2) - activestorage (6.0.2.2) - actionpack (= 6.0.2.2) - activejob (= 6.0.2.2) - activerecord (= 6.0.2.2) + activemodel (6.0.3.1) + activesupport (= 6.0.3.1) + activerecord (6.0.3.1) + activemodel (= 6.0.3.1) + activesupport (= 6.0.3.1) + activestorage (6.0.3.1) + actionpack (= 6.0.3.1) + activejob (= 6.0.3.1) + activerecord (= 6.0.3.1) marcel (~> 0.3.1) - activesupport (6.0.2.2) + activesupport (6.0.3.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - zeitwerk (~> 2.2) + zeitwerk (~> 2.2, >= 2.2.2) acts-as-taggable-on (6.5.0) activerecord (>= 5.0, < 6.1) addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) + administrate (0.13.0) + actionpack (>= 4.2) + actionview (>= 4.2) + activerecord (>= 4.2) + autoprefixer-rails (>= 6.0) + datetime_picker_rails (~> 0.0.7) + jquery-rails (>= 4.0) + kaminari (>= 1.0) + momentjs-rails (~> 2.8) + sassc-rails (~> 2.1) + selectize-rails (~> 0.6) annotate (3.1.1) activerecord (>= 3.2, < 7.0) rake (>= 10.4, < 14.0) ast (2.4.0) attr_extras (6.2.3) + autoprefixer-rails (9.7.6) + execjs aws-eventstream (1.1.0) - aws-partitions (1.296.0) - aws-sdk-core (3.94.0) + aws-partitions (1.317.0) + aws-sdk-core (3.96.1) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.30.0) + aws-sdk-kms (1.31.0) aws-sdk-core (~> 3, >= 3.71.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.61.2) - aws-sdk-core (~> 3, >= 3.83.0) + aws-sdk-s3 (1.65.0) + aws-sdk-core (~> 3, >= 3.96.1) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) - aws-sigv4 (1.1.1) + aws-sigv4 (1.1.3) aws-eventstream (~> 1.0, >= 1.0.2) axiom-types (0.1.1) descendants_tracker (~> 0.0.4) @@ -120,8 +126,8 @@ GEM bindex (0.8.1) bootsnap (1.4.6) msgpack (~> 1.0) - brakeman (4.8.1) - browser (4.0.0) + brakeman (4.8.2) + browser (4.1.0) builder (3.2.4) bullet (6.1.0) activesupport (>= 3.0.0) @@ -131,7 +137,7 @@ GEM bundler-audit (0.6.1) bundler (>= 1.2.0, < 3) thor (~> 0.18) - byebug (11.1.1) + byebug (11.1.3) chargebee (2.7.5) json_pure (~> 2.1) rest-client (>= 1.8, < 3.0) @@ -141,6 +147,8 @@ GEM concurrent-ruby (1.1.6) connection_pool (2.2.2) crass (1.0.6) + datetime_picker_rails (0.0.7) + momentjs-rails (>= 2.8.1) declarative (0.0.10) declarative-option (0.1.0) descendants_tracker (0.0.4) @@ -167,13 +175,13 @@ GEM equalizer (0.0.11) erubi (1.9.0) execjs (2.7.0) - facebook-messenger (1.4.1) + facebook-messenger (1.5.0) httparty (~> 0.13, >= 0.13.7) rack (>= 1.4.5) - factory_bot (5.1.2) + factory_bot (5.2.0) activesupport (>= 4.2.0) - factory_bot_rails (5.1.1) - factory_bot (~> 5.1.0) + factory_bot_rails (5.2.0) + factory_bot (~> 5.2.0) railties (>= 4.2.0) faker (2.11.0) i18n (>= 1.6, < 2) @@ -186,7 +194,7 @@ GEM foreman (0.87.1) globalid (0.4.2) activesupport (>= 4.2.0) - google-api-client (0.38.0) + google-api-client (0.39.4) addressable (~> 2.5, >= 2.5.1) googleauth (~> 0.9) httpclient (>= 2.8.1, < 3.0) @@ -200,7 +208,7 @@ GEM google-cloud-env (1.3.1) faraday (>= 0.17.3, < 2.0) google-cloud-errors (1.0.0) - google-cloud-storage (1.26.0) + google-cloud-storage (1.26.1) addressable (~> 2.5) digest-crc (~> 0.4) google-api-client (~> 0.33) @@ -217,7 +225,7 @@ GEM groupdate (5.0.0) activesupport (>= 5) haikunator (1.1.0) - hana (1.3.5) + hana (1.3.6) hashie (4.1.0) hkdf (0.3.0) http-accept (1.7.0) @@ -231,25 +239,28 @@ GEM concurrent-ruby (~> 1.0) ice_nine (0.11.2) inflecto (0.0.2) - jaro_winkler (1.5.4) jbuilder (2.10.0) activesupport (>= 5.0.0) jmespath (1.4.0) + jquery-rails (4.4.0) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) json (2.3.0) json_pure (2.3.0) jwt (2.2.1) - kaminari (1.2.0) + kaminari (1.2.1) activesupport (>= 4.1.0) - kaminari-actionview (= 1.2.0) - kaminari-activerecord (= 1.2.0) - kaminari-core (= 1.2.0) - kaminari-actionview (1.2.0) + kaminari-actionview (= 1.2.1) + kaminari-activerecord (= 1.2.1) + kaminari-core (= 1.2.1) + kaminari-actionview (1.2.1) actionview - kaminari-core (= 1.2.0) - kaminari-activerecord (1.2.0) + kaminari-core (= 1.2.1) + kaminari-activerecord (1.2.1) activerecord - kaminari-core (= 1.2.0) - kaminari-core (1.2.0) + kaminari-core (= 1.2.1) + kaminari-core (1.2.1) koala (3.0.0) addressable faraday @@ -272,12 +283,14 @@ GEM method_source (1.0.0) mime-types (3.3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2019.1009) - mimemagic (0.3.4) + mime-types-data (3.2020.0512) + mimemagic (0.3.5) mini_magick (4.10.1) mini_mime (1.0.2) mini_portile2 (2.4.0) - minitest (5.14.0) + minitest (5.14.1) + momentjs-rails (2.20.1) + railties (>= 3.1) msgpack (1.3.3) multi_json (1.14.1) multi_xml (0.6.0) @@ -290,7 +303,7 @@ GEM orm_adapter (0.5.0) os (1.1.0) parallel (1.19.1) - parser (2.7.1.1) + parser (2.7.1.2) ast (~> 2.4.0) pg (1.2.3) pry (0.13.1) @@ -298,8 +311,8 @@ GEM method_source (~> 1.0) pry-rails (0.3.9) pry (>= 0.10.4) - public_suffix (4.0.4) - puma (4.3.3) + public_suffix (4.0.5) + puma (4.3.5) nio4r (~> 2.0) pundit (2.1.0) activesupport (>= 3.0.0) @@ -314,38 +327,38 @@ GEM rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (6.0.2.2) - actioncable (= 6.0.2.2) - actionmailbox (= 6.0.2.2) - actionmailer (= 6.0.2.2) - actionpack (= 6.0.2.2) - actiontext (= 6.0.2.2) - actionview (= 6.0.2.2) - activejob (= 6.0.2.2) - activemodel (= 6.0.2.2) - activerecord (= 6.0.2.2) - activestorage (= 6.0.2.2) - activesupport (= 6.0.2.2) + rails (6.0.3.1) + actioncable (= 6.0.3.1) + actionmailbox (= 6.0.3.1) + actionmailer (= 6.0.3.1) + actionpack (= 6.0.3.1) + actiontext (= 6.0.3.1) + actionview (= 6.0.3.1) + activejob (= 6.0.3.1) + activemodel (= 6.0.3.1) + activerecord (= 6.0.3.1) + activestorage (= 6.0.3.1) + activesupport (= 6.0.3.1) bundler (>= 1.3.0) - railties (= 6.0.2.2) + railties (= 6.0.3.1) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-html-sanitizer (1.3.0) loofah (~> 2.3) - railties (6.0.2.2) - actionpack (= 6.0.2.2) - activesupport (= 6.0.2.2) + railties (6.0.3.1) + actionpack (= 6.0.3.1) + activesupport (= 6.0.3.1) method_source rake (>= 0.8.7) thor (>= 0.20.3, < 2.0) rainbow (3.0.0) rake (13.0.1) - rb-fsevent (0.10.3) + rb-fsevent (0.10.4) rb-inotify (0.10.1) ffi (~> 1.0) - redis (4.1.3) + redis (4.1.4) redis-namespace (1.7.0) redis (>= 3.0.4) redis-rack-cache (2.2.1) @@ -367,15 +380,15 @@ GEM netrc (~> 0.8) retriable (3.1.2) rexml (3.2.4) - rspec-core (3.9.1) - rspec-support (~> 3.9.1) - rspec-expectations (3.9.1) + rspec-core (3.9.2) + rspec-support (~> 3.9.3) + rspec-expectations (3.9.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.9.0) rspec-mocks (3.9.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.9.0) - rspec-rails (4.0.0) + rspec-rails (4.0.1) actionpack (>= 4.2) activesupport (>= 4.2) railties (>= 4.2) @@ -383,9 +396,8 @@ GEM rspec-expectations (~> 3.9) rspec-mocks (~> 3.9) rspec-support (~> 3.9) - rspec-support (3.9.2) - rubocop (0.81.0) - jaro_winkler (~> 1.5.1) + rspec-support (3.9.3) + rubocop (0.83.0) parallel (~> 1.10) parser (>= 2.7.0.1) rainbow (>= 2.2.2, < 4.0) @@ -398,7 +410,7 @@ GEM activesupport rack (>= 1.1) rubocop (>= 0.72.0) - rubocop-rspec (1.38.1) + rubocop-rspec (1.39.0) rubocop (>= 0.68.1) ruby-progressbar (1.10.1) sass (3.7.4) @@ -406,6 +418,14 @@ GEM sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) + sassc (2.3.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt scout_apm (2.6.7) parser scss_lint (0.59.0) @@ -413,12 +433,13 @@ GEM seed_dump (3.3.1) activerecord (>= 4) activesupport (>= 4) + selectize-rails (0.12.6) semantic_range (2.3.0) sentry-raven (3.0.0) faraday (>= 1.0) shoulda-matchers (4.3.0) activesupport (>= 4.2.0) - sidekiq (6.0.6) + sidekiq (6.0.7) connection_pool (>= 2.2.2) rack (~> 2.0) rack-protection (>= 2.0.0) @@ -448,9 +469,10 @@ GEM faraday inflecto virtus - telephone_number (1.4.6) + telephone_number (1.4.7) thor (0.20.3) thread_safe (0.3.6) + tilt (2.0.10) time_diff (0.3.0) activesupport i18n @@ -458,9 +480,11 @@ GEM faraday (~> 1.0.0) jwt (>= 1.5, <= 2.5) nokogiri (>= 1.6, < 2.0) + twitty (0.1.1) + oauth tzinfo (1.2.7) thread_safe (~> 0.1) - tzinfo-data (1.2019.3) + tzinfo-data (1.2020.1) tzinfo (>= 1.0.0) uber (0.1.0) uglifier (4.2.0) @@ -480,12 +504,12 @@ GEM equalizer (~> 0.0, >= 0.0.9) warden (1.2.8) rack (>= 2.0.6) - web-console (4.0.1) + web-console (4.0.2) actionview (>= 6.0.0) activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webpacker (5.0.1) + webpacker (5.1.1) activesupport (>= 5.2) rack-proxy (>= 0.6.1) railties (>= 5.2) @@ -505,6 +529,7 @@ PLATFORMS DEPENDENCIES action-cable-testing acts-as-taggable-on + administrate annotate attr_extras aws-sdk-s3 @@ -566,7 +591,7 @@ DEPENDENCIES telephone_number time_diff twilio-ruby (~> 5.32.0) - twitty! + twitty tzinfo-data uglifier valid_email2 @@ -576,7 +601,7 @@ DEPENDENCIES wisper (= 2.0.0) RUBY VERSION - ruby 2.7.0p0 + ruby 2.7.1p83 BUNDLED WITH - 2.1.2 + 2.1.4 diff --git a/Procfile b/Procfile index 16a5cc0c1..34f447dda 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,3 @@ +release: bundle exec rake db:migrate web: bin/rails server -p $PORT -e $RAILS_ENV -worker: bundle exec sidekiq -C config/sidekiq.yml \ No newline at end of file +worker: bundle exec sidekiq -C config/sidekiq.yml diff --git a/README.md b/README.md index e71b8c6d1..75a254058 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ ___ - - + +
![ChatUI progess](https://s3.us-west-2.amazonaws.com/gh-assets.chatwoot.com/chatwoot-dashboard-assets.png) diff --git a/app.json b/app.json index 0b00d971e..300f363db 100644 --- a/app.json +++ b/app.json @@ -3,7 +3,7 @@ "description": "Chatwoot is a customer support tool for instant messaging channels", "website": "https://www.chatwoot.com/", "repository": "https://github.com/chatwoot/chatwoot", - "logo": "data:image/jpeg;base64,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", + "logo": "https://app.chatwoot.com/brand-assets/logo_thumbnail.svg", "keywords": [ "live chat", "customer support", @@ -27,6 +27,10 @@ "RAILS_ENV": { "description": "Environment for rails middleware.", "value": "production" + }, + "FRONTEND_URL": { + "description": "Public root URL of the Chatwoot installation. This will be used in the emails.", + "value": "https://CHANGE.herokuapp.com" } }, "formation": { diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index ac907b367..f5e0f5476 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -1 +1,4 @@ //= link_tree ../images +//= link administrate/application.css +//= link administrate/application.js +//= link dashboardChart.js diff --git a/app/assets/javascripts/dashboardChart.js b/app/assets/javascripts/dashboardChart.js new file mode 100644 index 000000000..6bfe56bda --- /dev/null +++ b/app/assets/javascripts/dashboardChart.js @@ -0,0 +1,55 @@ +// eslint-disable-next-line +function prepareData(data) { + var labels = []; + var dataSet = []; + data.forEach(item => { + labels.push(item[0]); + dataSet.push(item[1]); + }); + return { labels, dataSet }; +} + +function getChartOptions() { + var fontFamily = + 'Inter,-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif'; + return { + responsive: true, + legend: { labels: { fontFamily } }, + scales: { + xAxes: [ + { + barPercentage: 1.26, + ticks: { fontFamily }, + gridLines: { display: false }, + }, + ], + yAxes: [ + { + ticks: { fontFamily }, + gridLines: { display: false }, + }, + ], + }, + }; +} + +// eslint-disable-next-line +function drawSuperAdminDashboard(data) { + var ctx = document.getElementById('dashboard-chart').getContext('2d'); + var chartData = prepareData(data); + // eslint-disable-next-line + new Chart(ctx, { + type: 'bar', + data: { + labels: chartData.labels, + datasets: [ + { + label: 'Conversations', + data: chartData.dataSet, + backgroundColor: '#1f93ff', + }, + ], + }, + options: getChartOptions(), + }); +} diff --git a/app/assets/stylesheets/administrate/application.scss b/app/assets/stylesheets/administrate/application.scss new file mode 100644 index 000000000..79738bbf3 --- /dev/null +++ b/app/assets/stylesheets/administrate/application.scss @@ -0,0 +1,32 @@ +@charset 'utf-8'; + +@import 'reset/normalize'; + +@import 'utilities/variables'; +@import 'utilities/text-color'; + +@import 'selectize'; +@import 'datetime_picker'; + +@import 'library/clearfix'; +@import 'library/data-label'; +@import 'library/variables'; + +@import 'base/forms'; +@import 'base/layout'; +@import 'base/lists'; +@import 'base/tables'; +@import 'base/typography'; + +@import 'components/app-container'; +@import 'components/attributes'; +@import 'components/buttons'; +@import 'components/cells'; +@import 'components/field-unit'; +@import 'components/flashes'; +@import 'components/form-actions'; +@import 'components/main-content'; +@import 'components/navigation'; +@import 'components/pagination'; +@import 'components/search'; +@import 'components/reports'; diff --git a/app/assets/stylesheets/administrate/base/_forms.scss b/app/assets/stylesheets/administrate/base/_forms.scss new file mode 100644 index 000000000..bf014a746 --- /dev/null +++ b/app/assets/stylesheets/administrate/base/_forms.scss @@ -0,0 +1,103 @@ +fieldset { + background-color: transparent; + border: 0; + margin: 0; + padding: 0; +} + +legend { + font-weight: $font-weight-medium; + margin: 0; + padding: 0; +} + +label { + display: block; + font-weight: $font-weight-medium; + margin: 0; +} + +input, +select { + display: block; + font-family: $base-font-family; + font-size: $base-font-size; +} + +input, +select, +textarea { + display: block; + font-family: $base-font-family; + font-size: 16px; +} + +[type="color"], +[type="date"], +[type="datetime-local"], +[type="email"], +[type="month"], +[type="number"], +[type="password"], +[type="search"], +[type="tel"], +[type="text"], +[type="time"], +[type="url"], +[type="week"], +input:not([type]), +textarea { + appearance: none; + background-color: $white; + border: $base-border; + border-radius: $base-border-radius; + padding: 0.5em; + transition: border-color $base-duration $base-timing; + width: 100%; + + &:hover { + border-color: mix($black, $base-border-color, 20%); + } + + &:focus { + border-color: $action-color; + outline: none; + } + + &:disabled { + background-color: mix($black, $white, 5%); + cursor: not-allowed; + + &:hover { + border: $base-border; + } + } +} + +textarea { + resize: vertical; +} + +[type="checkbox"], +[type="radio"] { + display: inline; + margin-right: $small-spacing / 2; +} + +[type="file"] { + width: 100%; +} + +select { + width: 100%; +} + +[type="checkbox"], +[type="radio"], +[type="file"], +select { + &:focus { + outline: $focus-outline; + outline-offset: $focus-outline-offset; + } +} diff --git a/app/assets/stylesheets/administrate/base/_layout.scss b/app/assets/stylesheets/administrate/base/_layout.scss new file mode 100644 index 000000000..c4c081a82 --- /dev/null +++ b/app/assets/stylesheets/administrate/base/_layout.scss @@ -0,0 +1,22 @@ +html { + background-color: $color-white; + box-sizing: border-box; + font-size: 10px; + -webkit-font-smoothing: antialiased; +} + +*, +*::before, +*::after { + box-sizing: inherit; +} + +figure { + margin: 0; +} + +img, +picture { + margin: 0; + max-width: 100%; +} diff --git a/app/assets/stylesheets/administrate/base/_lists.scss b/app/assets/stylesheets/administrate/base/_lists.scss new file mode 100644 index 000000000..70eae5203 --- /dev/null +++ b/app/assets/stylesheets/administrate/base/_lists.scss @@ -0,0 +1,19 @@ +ul, +ol { + list-style-type: none; + margin: 0; + padding: 0; +} + +dl { + margin-bottom: $small-spacing; + + dt { + font-weight: $font-weight-medium; + margin-top: $small-spacing; + } + + dd { + margin: 0; + } +} diff --git a/app/assets/stylesheets/administrate/base/_tables.scss b/app/assets/stylesheets/administrate/base/_tables.scss new file mode 100644 index 000000000..1772e8cf4 --- /dev/null +++ b/app/assets/stylesheets/administrate/base/_tables.scss @@ -0,0 +1,71 @@ +table { + border-collapse: collapse; + font-size: $font-size-default; + text-align: left; + width: 100%; + + a { + color: inherit; + text-decoration: none; + } +} + +tr { + border-bottom: $base-border; + + th { + font-weight: $font-weight-medium; + + &.cell-label--avatar-field { + a { + display: none; + } + } + } +} + +tbody tr { + &:hover { + background-color: $base-background-color; + cursor: pointer; + } + + &:focus { + outline: $focus-outline; + outline-offset: -($focus-outline-width); + } + + td { + &.cell-data--avatar-field { + line-height: 1; + text-align: center; + + img { + border-radius: 50%; + height: $space-large; + max-height: $space-large; + width: $space-large; + } + } + } +} + +td, +th { + padding: $space-slab; + vertical-align: middle; +} + +td:first-child, +th:first-child { + padding-left: 0; +} + +td:last-child, +th:last-child { + padding-right: 0; +} + +td img { + max-height: 2rem; +} diff --git a/app/assets/stylesheets/administrate/base/_typography.scss b/app/assets/stylesheets/administrate/base/_typography.scss new file mode 100644 index 000000000..bf2c2d9c2 --- /dev/null +++ b/app/assets/stylesheets/administrate/base/_typography.scss @@ -0,0 +1,44 @@ +body { + color: $base-font-color; + font-family: $base-font-family; + font-size: $base-font-size; + line-height: $base-line-height; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: $heading-font-family; + font-size: $base-font-size; + line-height: $heading-line-height; + margin: 0; +} + +p { + margin: 0 0 $small-spacing; +} + +a { + color: $action-color; + transition: color $base-duration $base-timing; + + &:hover { + color: mix($black, $action-color, 25%); + } + + &:focus { + outline: $focus-outline; + outline-offset: $focus-outline-offset; + } +} + +hr { + border-bottom: $base-border; + border-left: 0; + border-right: 0; + border-top: 0; + margin: $base-spacing 0; +} diff --git a/app/assets/stylesheets/administrate/components/_app-container.scss b/app/assets/stylesheets/administrate/components/_app-container.scss new file mode 100644 index 000000000..80873272f --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_app-container.scss @@ -0,0 +1,8 @@ +.app-container { + align-items: stretch; + display: flex; + margin-left: auto; + margin-right: auto; + max-width: 100rem; + min-height: 100vh; +} diff --git a/app/assets/stylesheets/administrate/components/_attributes.scss b/app/assets/stylesheets/administrate/components/_attributes.scss new file mode 100644 index 000000000..713d9f523 --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_attributes.scss @@ -0,0 +1,26 @@ +.attribute-label { + @include data-label; + clear: left; + float: left; + margin-bottom: $base-spacing; + margin-top: 0.25em; + text-align: right; + width: calc(15% - 1rem); +} + +.preserve-whitespace { + white-space: pre-wrap; + word-wrap: break-word; +} + +.attribute-data { + float: left; + margin-bottom: $base-spacing; + margin-left: 2rem; + width: calc(85% - 1rem); +} + +.attribute--nested { + border: $base-border; + padding: $small-spacing; +} diff --git a/app/assets/stylesheets/administrate/components/_buttons.scss b/app/assets/stylesheets/administrate/components/_buttons.scss new file mode 100644 index 000000000..3e021e658 --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_buttons.scss @@ -0,0 +1,50 @@ +button, +input[type="button"], +input[type="reset"], +input[type="submit"], +.button { + appearance: none; + background-color: $color-woot; + border: 0; + border-radius: $base-border-radius; + color: $white; + cursor: pointer; + display: inline-block; + font-size: $font-size-default; + -webkit-font-smoothing: antialiased; + font-weight: $font-weight-medium; + line-height: 1; + padding: $space-one $space-two; + text-decoration: none; + transition: background-color $base-duration $base-timing; + user-select: none; + vertical-align: middle; + white-space: nowrap; + + &:hover { + background-color: mix($black, $color-woot, 20%); + color: $white; + } + + &:focus { + outline: $focus-outline; + outline-offset: $focus-outline-offset; + } + + &:disabled { + cursor: not-allowed; + opacity: 0.5; + + &:hover { + background-color: $color-woot; + } + } +} + +.button--alt { + background-color: transparent; + border: $base-border; + border-color: $blue; + color: $blue; + margin-bottom: $base-spacing; +} diff --git a/app/assets/stylesheets/administrate/components/_cells.scss b/app/assets/stylesheets/administrate/components/_cells.scss new file mode 100644 index 000000000..2f7e27c4a --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_cells.scss @@ -0,0 +1,45 @@ +.cell-label { + &:hover { + a { + color: $action-color; + } + + svg { + fill: $action-color; + transform: rotate(180deg); + } + } + + a { + color: inherit; + display: inline-block; + transition: color $base-duration $base-timing; + width: 100%; + } +} + +.cell-label--asc, +.cell-label--desc { + font-weight: $font-weight-medium; +} + +.cell-label__sort-indicator { + float: right; + margin-left: 5px; + + svg { + fill: $hint-grey; + height: 13px; + transition: transform $base-duration $base-timing; + width: 13px; + } +} + +.cell-label__sort-indicator--desc { + transform: rotate(180deg); +} + +.cell-data--number, +.cell-label--number { + text-align: right; +} diff --git a/app/assets/stylesheets/administrate/components/_field-unit.scss b/app/assets/stylesheets/administrate/components/_field-unit.scss new file mode 100644 index 000000000..856c1872c --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_field-unit.scss @@ -0,0 +1,54 @@ +.field-unit { + @include administrate-clearfix; + align-items: center; + display: flex; + margin-bottom: $base-spacing; + position: relative; + width: 100%; +} + +.field-unit__label { + float: left; + margin-left: 1rem; + text-align: right; + width: calc(15% - 1rem); +} + +.field-unit__field { + float: left; + margin-left: 2rem; + max-width: 50rem; + width: 100%; +} + +.field-unit--nested { + border: $base-border; + margin-left: 7.5%; + max-width: 60rem; + padding: $small-spacing; + width: 100%; + + .field-unit__field { + width: 100%; + } + + .field-unit__label { + width: 10rem; + } +} + +.field-unit--required { + label::after { + color: $red; + content: ' *'; + } +} + +.attribute-data--avatar-field { + height: $space-larger; + width: $space-larger; + + img { + border-radius: 50%; + } +} diff --git a/app/assets/stylesheets/administrate/components/_flashes.scss b/app/assets/stylesheets/administrate/components/_flashes.scss new file mode 100644 index 000000000..48c3e685c --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_flashes.scss @@ -0,0 +1,28 @@ +$base-spacing: 1.5em !default; +$flashes: ( + "alert": #fff6bf, + "error": #fbe3e4, + "notice": #e5edf8, + "success": #e6efc2, +) !default; + +@each $flash-type, $color in $flashes { + .flash-#{$flash-type} { + background-color: $color; + color: mix($black, $color, 60%); + display: block; + margin-bottom: $base-spacing / 2; + padding: $base-spacing / 2; + text-align: center; + + a { + color: mix($black, $color, 70%); + text-decoration: underline; + + &:focus, + &:hover { + color: mix($black, $color, 90%); + } + } + } +} diff --git a/app/assets/stylesheets/administrate/components/_form-actions.scss b/app/assets/stylesheets/administrate/components/_form-actions.scss new file mode 100644 index 000000000..d87d17435 --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_form-actions.scss @@ -0,0 +1,3 @@ +.form-actions { + margin-left: calc(15% + 2rem); +} diff --git a/app/assets/stylesheets/administrate/components/_main-content.scss b/app/assets/stylesheets/administrate/components/_main-content.scss new file mode 100644 index 000000000..d03229828 --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_main-content.scss @@ -0,0 +1,26 @@ +.main-content { + font-size: $font-size-default; + left: 23rem; + position: absolute; + right: 0; + top: 0; +} + +.main-content__body { + padding: $space-two; +} + +.main-content__header { + align-items: center; + background-color: $color-white; + border-bottom: 1px solid $color-border; + display: flex; + min-height: 5.6rem; + padding: $space-small $space-normal; +} + +.main-content__page-title { + font-size: $font-size-large; + font-weight: $font-weight-medium; + margin-right: auto; +} diff --git a/app/assets/stylesheets/administrate/components/_navigation.scss b/app/assets/stylesheets/administrate/components/_navigation.scss new file mode 100644 index 000000000..f6b1a641d --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_navigation.scss @@ -0,0 +1,72 @@ +.logo-brand { + margin-bottom: $space-normal; + padding: $space-normal $space-smaller; + text-align: center; +} + +.navigation { + background: $white; + border-right: 1px solid $color-border; + display: flex; + flex-direction: column; + font-size: $font-size-default; + font-weight: $font-weight-medium; + height: 100%; + justify-content: flex-start; + left: 0; + margin: 0; + overflow: auto; + padding: $space-normal; + position: fixed; + top: 0; + width: 23rem; + z-index: 1023; + + li { + align-items: center; + display: flex; + + a { + color: $color-gray; + text-decoration: none; + } + + i { + min-width: $space-medium; + } + } +} + +.navigation__link { + background-color: transparent; + color: $color-gray; + display: block; + line-height: 1; + margin-bottom: $space-smaller; + padding: $space-one; + + &:hover { + color: $blue; + + a { + color: $blue; + } + } + + + &.navigation__link--active { + background-color: $color-background; + border-radius: $base-border-radius; + color: $blue; + + a { + color: $blue; + } + } +} + +.logout { + bottom: $space-normal; + left: $space-normal; + position: fixed; +} diff --git a/app/assets/stylesheets/administrate/components/_pagination.scss b/app/assets/stylesheets/administrate/components/_pagination.scss new file mode 100644 index 000000000..cb3e12f21 --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_pagination.scss @@ -0,0 +1,19 @@ +.pagination { + font-size: $font-size-default; + margin-top: $base-spacing; + padding-left: $base-spacing; + padding-right: $base-spacing; + text-align: center; + + .first, + .prev, + .page, + .next, + .last { + margin: $small-spacing; + } + + .current { + font-weight: $font-weight-medium; + } +} diff --git a/app/assets/stylesheets/administrate/components/_reports.scss b/app/assets/stylesheets/administrate/components/_reports.scss new file mode 100644 index 000000000..0c8c133b7 --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_reports.scss @@ -0,0 +1,15 @@ +.report--list { + display: flex; + padding: 0 $space-two $space-larger; +} + +.report-card { + flex: 1; + font-size: $font-size-small; + text-align: center; + + .metric { + font-size: $font-size-bigger; + font-weight: 200; + } +} diff --git a/app/assets/stylesheets/administrate/components/_search.scss b/app/assets/stylesheets/administrate/components/_search.scss new file mode 100644 index 000000000..f3a259618 --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_search.scss @@ -0,0 +1,44 @@ +.search { + margin-left: auto; + margin-right: 2rem; + max-width: 44rem; + position: relative; + width: 100%; +} + +.search__input { + background: $grey-1; + padding-left: $space-normal * 2.5; + padding-right: $space-normal * 2.5; +} + +.search__eyeglass-icon { + fill: $grey-7; + height: $space-normal; + left: $space-normal; + position: absolute; + top: 50%; + transform: translateY(-50%); + width: $space-normal; +} + +.search__clear-link { + height: $space-normal; + position: absolute; + right: $space-normal * 0.75; + top: 50%; + transform: translateY(-50%); + width: $space-normal; +} + +.search__clear-icon { + fill: $grey-5; + height: $space-normal; + position: absolute; + transition: fill $base-duration $base-timing; + width: $space-normal; + + &:hover { + fill: $action-color; + } +} diff --git a/app/assets/stylesheets/administrate/library/_clearfix.scss b/app/assets/stylesheets/administrate/library/_clearfix.scss new file mode 100644 index 000000000..ea852351f --- /dev/null +++ b/app/assets/stylesheets/administrate/library/_clearfix.scss @@ -0,0 +1,7 @@ +@mixin administrate-clearfix { + &::after { + clear: both; + content: ''; + display: block; + } +} diff --git a/app/assets/stylesheets/administrate/library/_data-label.scss b/app/assets/stylesheets/administrate/library/_data-label.scss new file mode 100644 index 000000000..2efcd2836 --- /dev/null +++ b/app/assets/stylesheets/administrate/library/_data-label.scss @@ -0,0 +1,8 @@ +@mixin data-label { + color: $hint-grey; + font-size: 0.8em; + font-weight: 400; + letter-spacing: 0.0357em; + position: relative; + text-transform: uppercase; +} diff --git a/app/assets/stylesheets/administrate/library/_variables.scss b/app/assets/stylesheets/administrate/library/_variables.scss new file mode 100644 index 000000000..3fdfcfd8d --- /dev/null +++ b/app/assets/stylesheets/administrate/library/_variables.scss @@ -0,0 +1,61 @@ +// Typography +$base-font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif !default; +$heading-font-family: $base-font-family !default; + +$base-font-size: 10px !default; + +$base-line-height: 1.5 !default; +$heading-line-height: 1.2 !default; + +// Other Sizes +$base-border-radius: 4px !default; +$base-spacing: $base-line-height * 1em !default; +$small-spacing: $base-spacing / 2 !default; + +// Colors +$white: #fff !default; +$black: #000 !default; + +$blue: #1f93ff !default; +$red: #ff382d !default; +$light-yellow: #ffc532 !default; +$light-green: #44ce4b !default; + +$grey-0: #f6f7f7 !default; +$grey-1: #f0f4f5 !default; +$grey-2: #cfd8dc !default; +$grey-5: #adb5bd !default; +$grey-7: #293f54 !default; + +$hint-grey: #7b808c !default; + +// Font Colors +$base-font-color: $grey-7 !default; +$action-color: $blue !default; + +// Background Colors +$base-background-color: $grey-0 !default; + +// Focus +$focus-outline-color: transparentize($action-color, 0.4); +$focus-outline-width: 3px; +$focus-outline: $focus-outline-width solid $focus-outline-color; +$focus-outline-offset: 1px; + +// Flash Colors +$flash-colors: ( + alert: $light-yellow, + error: $red, + notice: mix($white, $blue, 50%), + success: $light-green +); + +// Border +$base-border-color: $grey-1 !default; +$base-border: 1px solid $base-border-color !default; + +// Transitions +$base-duration: 250ms !default; +$base-timing: ease-in-out !default; diff --git a/app/assets/stylesheets/administrate/reset/_normalize.scss b/app/assets/stylesheets/administrate/reset/_normalize.scss new file mode 100644 index 000000000..fa4e73dd4 --- /dev/null +++ b/app/assets/stylesheets/administrate/reset/_normalize.scss @@ -0,0 +1,447 @@ +/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in + * IE on Windows Phone and in iOS. + */ + +html { + line-height: 1.15; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers (opinionated). + */ + +body { + margin: 0; +} + +/** + * Add the correct display in IE 9-. + */ + +article, +aside, +footer, +header, +nav, +section { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + * 1. Add the correct display in IE. + */ + +figcaption, +figure, +main { /* 1 */ + display: block; +} + +/** + * Add the correct margin in IE 8. + */ + +figure { + margin: 1em 40px; +} + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * 1. Remove the gray background on active links in IE 10. + * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. + */ + +a { + background-color: transparent; /* 1 */ + -webkit-text-decoration-skip: objects; /* 2 */ +} + +/** + * 1. Remove the bottom border in Chrome 57- and Firefox 39-. + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} + +/** + * Prevent the duplicate application of `bolder` by the next rule in Safari 6. + */ + +b, +strong { + font-weight: inherit; +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/** + * Add the correct font style in Android 4.3-. + */ + +dfn { + font-style: italic; +} + +/** + * Add the correct background and color in IE 9-. + */ + +mark { + background-color: #ff0; + color: #000; +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + */ + +audio, +video { + display: inline-block; +} + +/** + * Add the correct display in iOS 4-7. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Remove the border on images inside links in IE 10-. + */ + +img { + border-style: none; +} + +/** + * Hide the overflow in IE. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers (opinionated). + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: sans-serif; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { /* 1 */ + text-transform: none; +} + +/** + * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` + * controls in Android 4. + * 2. Correct the inability to style clickable types in iOS and Safari. + */ + +button, +html [type="button"], /* 1 */ +[type="reset"], +[type="submit"] { + -webkit-appearance: button; /* 2 */ +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} + +/** + * 1. Add the correct display in IE 9-. + * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Remove the default vertical scrollbar in IE. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10-. + * 2. Remove the padding in IE 10-. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/** + * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-cancel-button, +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in IE 9-. + * 1. Add the correct display in Edge, IE, and Firefox. + */ + +details, /* 1 */ +menu { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Scripting + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + */ + +canvas { + display: inline-block; +} + +/** + * Add the correct display in IE. + */ + +template { + display: none; +} + +/* Hidden + ========================================================================== */ + +/** + * Add the correct display in IE 10-. + */ + +[hidden] { + display: none; +} diff --git a/app/assets/stylesheets/administrate/utilities/_text-color.scss b/app/assets/stylesheets/administrate/utilities/_text-color.scss new file mode 100644 index 000000000..afa3bcca3 --- /dev/null +++ b/app/assets/stylesheets/administrate/utilities/_text-color.scss @@ -0,0 +1,3 @@ +.text-color-red { + color: $alert-color; +} diff --git a/app/assets/stylesheets/administrate/utilities/_variables.scss b/app/assets/stylesheets/administrate/utilities/_variables.scss new file mode 100644 index 000000000..1798d918b --- /dev/null +++ b/app/assets/stylesheets/administrate/utilities/_variables.scss @@ -0,0 +1,98 @@ +// Font sizes +$font-size-nano: 0.8rem; +$font-size-micro: 1.0rem; +$font-size-mini: 1.2rem; +$font-size-small: 1.4rem; +$font-size-default: 1.6rem; +$font-size-medium: 1.8rem; +$font-size-large: 2.2rem; +$font-size-big: 2.4rem; +$font-size-bigger: 3.0rem; +$font-size-mega: 3.4rem; +$font-size-giga: 4.0rem; + +// spaces +$zero: 0; +$space-micro: 0.2rem; +$space-smaller: 0.4rem; +$space-small: 0.8rem; +$space-one: 1rem; +$space-slab: 1.2rem; +$space-normal: 1.6rem; +$space-two: 2.0rem; +$space-medium: 2.4rem; +$space-large: 3.2rem; +$space-larger: 4.8rem; +$space-jumbo: 6.4rem; +$space-mega: 10.0rem; + +// font-weight +$font-weight-feather: 100; +$font-weight-light: 300; +$font-weight-normal: 400; +$font-weight-medium: 500; +$font-weight-bold: 600; +$font-weight-black: 700; + +//Navbar +$nav-bar-width: 23rem; +$header-height: 5.6rem; + +$woot-logo-padding: $space-large $space-two; + +// Colors +$color-woot: #1f93ff; +$color-gray: #6e6f73; +$color-light-gray: #999a9b; +$color-border: #e0e6ed; +$color-border-light: #f0f4f5; +$color-background: #f4f6fb; +$color-border-dark: #cad0d4; +$color-background-light: #f9fafc; +$color-white: #fff; +$color-body: #3c4858; +$color-heading: #1f2d3d; +$color-extra-light-blue: #f5f7f9; + +$primary-color: $color-woot; +$secondary-color: #35c5ff; +$success-color: #44ce4b; +$warning-color: #ffc532; +$alert-color: #ff382d; + +$masked-bg: rgba(0, 0, 0, .4); + +// Color-palettes + +$color-primary-light: #c7e3ff; +$color-primary-dark: darken($color-woot, 20%); + +// Thumbnail +$thumbnail-radius: 4rem; + +// chat-header +$conv-header-height: 4rem; + +// Inbox List + +$inbox-thumb-size: 4.8rem; + + +// Spinner +$spinkit-spinner-color: $color-white !default; +$spinkit-spinner-margin: 0 0 0 1.6rem !default; +$spinkit-size: 1.6rem !default; + +// Snackbar default +$woot-snackbar-bg: #323232; +$woot-snackbar-button: #ffeb3b; + +$swift-ease-out-duration: .4s !default; +$swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default; +$swift-ease-out: all $swift-ease-out-duration $swift-ease-out-timing-function !default; + +// Ionicons +$ionicons-font-path: '~ionicons/fonts'; + +// Transitions +$transition-ease-in: all 0.250s ease-in; diff --git a/app/bot/bot.rb b/app/bot/bot.rb deleted file mode 100644 index c834e7774..000000000 --- a/app/bot/bot.rb +++ /dev/null @@ -1,19 +0,0 @@ -# app/bot/facebook_bot.rb -require 'facebook/messenger' -include Facebook::Messenger - -Bot.on :message do |message| - response = ::Integrations::Facebook::MessageParser.new(message) - ::Integrations::Facebook::MessageCreator.new(response).perform -end - -Bot.on :delivery do |delivery| - # delivery.ids # => 'mid.1457764197618:41d102a3e1ae206a38' - # delivery.sender # => { 'id' => '1008372609250235' } - # delivery.recipient # => { 'id' => '2015573629214912' } - # delivery.at # => 2016-04-22 21:30:36 +0200 - # delivery.seq # => 37 - updater = Integrations::Facebook::DeliveryStatus.new(delivery) - updater.perform - puts "Human was online at #{delivery.at}" -end diff --git a/app/bot/facebook_bot.rb b/app/bot/facebook_bot.rb new file mode 100644 index 000000000..f0e04ebc2 --- /dev/null +++ b/app/bot/facebook_bot.rb @@ -0,0 +1,22 @@ +require 'facebook/messenger' + +class FacebookBot + include Facebook::Messenger + + Bot.on :message do |message| + Rails.logger.info "MESSAGE_RECIEVED #{message}" + response = ::Integrations::Facebook::MessageParser.new(message) + ::Integrations::Facebook::MessageCreator.new(response).perform + end + + Bot.on :delivery do |delivery| + # delivery.ids # => 'mid.1457764197618:41d102a3e1ae206a38' + # delivery.sender # => { 'id' => '1008372609250235' } + # delivery.recipient # => { 'id' => '2015573629214912' } + # delivery.at # => 2016-04-22 21:30:36 +0200 + # delivery.seq # => 37 + updater = Integrations::Facebook::DeliveryStatus.new(delivery) + updater.perform + Rails.logger.info "Human was online at #{delivery.at}" + end +end diff --git a/app/builders/account_builder.rb b/app/builders/account_builder.rb index 126eedce0..1c7c1f63d 100644 --- a/app/builders/account_builder.rb +++ b/app/builders/account_builder.rb @@ -2,7 +2,7 @@ class AccountBuilder include CustomExceptions::Account - pattr_initialize [:account_name!, :email!] + pattr_initialize [:account_name!, :email!, :confirmed!] def perform validate_email @@ -38,6 +38,7 @@ class AccountBuilder def create_account @account = Account.create!(name: @account_name) + Current.account = @account end def create_and_link_user @@ -46,6 +47,7 @@ class AccountBuilder password: password, password_confirmation: password, name: email_to_name(@email)) + @user.confirm if @confirmed if @user.save! link_user_to_account(@user, @account) @user diff --git a/app/builders/contact_builder.rb b/app/builders/contact_builder.rb index 9bb3ef3ac..3e362c9b5 100644 --- a/app/builders/contact_builder.rb +++ b/app/builders/contact_builder.rb @@ -14,6 +14,18 @@ class ContactBuilder @account ||= inbox.account end + def create_contact_inbox(contact) + ::ContactInbox.create!( + contact_id: contact.id, + inbox_id: inbox.id, + source_id: source_id + ) + end + + def update_contact_avatar(contact) + ::ContactAvatarJob.perform_later(contact, contact_attributes[:avatar_url]) if contact_attributes[:avatar_url] + end + def build_contact ActiveRecord::Base.transaction do contact = account.contacts.create!( @@ -23,16 +35,12 @@ class ContactBuilder identifier: contact_attributes[:identifier], additional_attributes: contact_attributes[:additional_attributes] ) - contact_inbox = ::ContactInbox.create!( - contact_id: contact.id, - inbox_id: inbox.id, - source_id: source_id - ) - ::ContactAvatarJob.perform_later(contact, contact_attributes[:avatar_url]) if contact_attributes[:avatar_url] + contact_inbox = create_contact_inbox(contact) + update_contact_avatar(contact) contact_inbox rescue StandardError => e - Rails.logger e + Rails.logger.info e end end end diff --git a/app/controllers/api/v1/accounts/accounts_controller.rb b/app/controllers/api/v1/accounts/accounts_controller.rb index 0fd5dc7cf..adb8bf11c 100644 --- a/app/controllers/api/v1/accounts/accounts_controller.rb +++ b/app/controllers/api/v1/accounts/accounts_controller.rb @@ -5,8 +5,8 @@ class Api::V1::Accounts::AccountsController < Api::BaseController skip_before_action :authenticate_user!, :set_current_user, :check_subscription, :handle_with_exception, only: [:create], raise: false before_action :check_signup_enabled, only: [:create] - before_action :check_authorization, except: [:create] before_action :fetch_account, except: [:create] + before_action :check_authorization, except: [:create] rescue_from CustomExceptions::Account::InvalidEmail, CustomExceptions::Account::UserExists, @@ -16,11 +16,12 @@ class Api::V1::Accounts::AccountsController < Api::BaseController def create @user = AccountBuilder.new( account_name: account_params[:account_name], - email: account_params[:email] + email: account_params[:email], + confirmed: confirmed? ).perform if @user send_auth_headers(@user) - render 'devise/auth.json', locals: { resource: @user } + render partial: 'devise/auth.json', locals: { resource: @user } else render_error_response(CustomExceptions::Account::SignupFailed.new({})) end @@ -34,14 +35,25 @@ class Api::V1::Accounts::AccountsController < Api::BaseController @account.update!(account_params.slice(:name, :locale, :domain, :support_email, :domain_emails_enabled)) end + def update_active_at + @current_account_user.active_at = Time.now.utc + @current_account_user.save! + head :ok + end + private def check_authorization authorize(Account) end + def confirmed? + super_admin? && params[:confirmed] + end + def fetch_account @account = current_user.accounts.find(params[:id]) + @current_account_user = @account.account_users.find_by(user_id: current_user.id) end def account_params @@ -51,4 +63,12 @@ class Api::V1::Accounts::AccountsController < Api::BaseController def check_signup_enabled raise ActionController::RoutingError, 'Not Found' if ENV.fetch('ENABLE_ACCOUNT_SIGNUP', true) == 'false' end + + def pundit_user + { + user: current_user, + account: @account, + account_user: @current_account_user + } + end end diff --git a/app/controllers/api/v1/accounts/agents_controller.rb b/app/controllers/api/v1/accounts/agents_controller.rb index ca796ceef..85a05fbc1 100644 --- a/app/controllers/api/v1/accounts/agents_controller.rb +++ b/app/controllers/api/v1/accounts/agents_controller.rb @@ -10,18 +10,18 @@ class Api::V1::Accounts::AgentsController < Api::BaseController end def destroy - @agent.account_user.destroy + @agent.current_account_user.destroy head :ok end def update @agent.update!(agent_params.except(:role)) - @agent.account_user.update!(role: agent_params[:role]) if agent_params[:role] - render 'api/v1/models/user.json', locals: { resource: @agent } + @agent.current_account_user.update!(role: agent_params[:role]) if agent_params[:role] + render partial: 'api/v1/models/agent.json.jbuilder', locals: { resource: @agent } end def create - render 'api/v1/models/user.json', locals: { resource: @user } + render partial: 'api/v1/models/agent.json.jbuilder', locals: { resource: @user } end private diff --git a/app/controllers/api/v1/accounts/callbacks_controller.rb b/app/controllers/api/v1/accounts/callbacks_controller.rb index bf42b215a..54c7bace4 100644 --- a/app/controllers/api/v1/accounts/callbacks_controller.rb +++ b/app/controllers/api/v1/accounts/callbacks_controller.rb @@ -14,7 +14,7 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController @facebook_inbox = current_account.inboxes.create!(name: inbox_name, channel: facebook_channel) set_avatar(@facebook_inbox, page_id) rescue StandardError => e - Rails.logger e + Rails.logger.info e end end @@ -62,7 +62,7 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController koala = Koala::Facebook::OAuth.new(ENV['FB_APP_ID'], ENV['FB_APP_SECRET']) koala.exchange_access_token_info(omniauth_token)['access_token'] rescue StandardError => e - Rails.logger e + Rails.logger.info e end def mark_already_existing_facebook_pages(data) diff --git a/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb b/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb index c0c121900..8011d3891 100644 --- a/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb +++ b/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb @@ -1,4 +1,5 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::BaseController + before_action :current_account before_action :authorize_request def create diff --git a/app/controllers/api/v1/accounts/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb index 05eaa5861..fcda432a5 100644 --- a/app/controllers/api/v1/accounts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/conversations_controller.rb @@ -1,5 +1,6 @@ class Api::V1::Accounts::ConversationsController < Api::BaseController include Events::Types + before_action :current_account before_action :conversation, except: [:index] before_action :contact_inbox, only: [:create] @@ -20,8 +21,18 @@ class Api::V1::Accounts::ConversationsController < Api::BaseController def show; end + def mute + @conversation.mute! + head :ok + end + def toggle_status - @status = @conversation.toggle_status + if params[:status] + @conversation.status = params[:status] + @status = @conversation.save + else + @status = @conversation.toggle_status + end end def toggle_typing_status diff --git a/app/controllers/api/v1/accounts/inboxes_controller.rb b/app/controllers/api/v1/accounts/inboxes_controller.rb index 4b3ed836e..8fdde8cc0 100644 --- a/app/controllers/api/v1/accounts/inboxes_controller.rb +++ b/app/controllers/api/v1/accounts/inboxes_controller.rb @@ -1,7 +1,8 @@ class Api::V1::Accounts::InboxesController < Api::BaseController - before_action :check_authorization + before_action :current_account before_action :fetch_inbox, except: [:index, :create] before_action :fetch_agent_bot, only: [:set_agent_bot] + before_action :check_authorization def index @inboxes = policy_scope(current_account.inboxes) diff --git a/app/controllers/api/v1/accounts/webhooks_controller.rb b/app/controllers/api/v1/accounts/webhooks_controller.rb index dbdd953ed..d3afba2af 100644 --- a/app/controllers/api/v1/accounts/webhooks_controller.rb +++ b/app/controllers/api/v1/accounts/webhooks_controller.rb @@ -1,4 +1,5 @@ class Api::V1::Accounts::WebhooksController < Api::BaseController + before_action :current_account before_action :check_authorization before_action :fetch_webhook, only: [:update, :destroy] diff --git a/app/controllers/api/v1/profiles_controller.rb b/app/controllers/api/v1/profiles_controller.rb index 9a0bbfc17..bd415ffeb 100644 --- a/app/controllers/api/v1/profiles_controller.rb +++ b/app/controllers/api/v1/profiles_controller.rb @@ -2,7 +2,7 @@ class Api::V1::ProfilesController < Api::BaseController before_action :set_user def show - render json: @user + render partial: 'api/v1/models/user.json.jbuilder', locals: { resource: @user } end def update diff --git a/app/controllers/api/v1/widget/conversations_controller.rb b/app/controllers/api/v1/widget/conversations_controller.rb index 8f8e372fe..cdfbd9d4a 100644 --- a/app/controllers/api/v1/widget/conversations_controller.rb +++ b/app/controllers/api/v1/widget/conversations_controller.rb @@ -3,6 +3,10 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController before_action :set_web_widget before_action :set_contact + def index + @conversation = conversation + end + def toggle_typing head :ok && return if conversation.nil? diff --git a/app/controllers/api/v2/accounts/reports_controller.rb b/app/controllers/api/v2/accounts/reports_controller.rb index fe94db4e1..6aa0355a4 100644 --- a/app/controllers/api/v2/accounts/reports_controller.rb +++ b/app/controllers/api/v2/accounts/reports_controller.rb @@ -11,10 +11,6 @@ class Api::V2::Accounts::ReportsController < Api::BaseController private - def current_account - current_user.account - end - def account_summary_params { type: :account, diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b6473cf13..ae3e5e857 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -14,7 +14,8 @@ class ApplicationController < ActionController::Base private def current_account - @_ ||= find_current_account + @current_account ||= find_current_account + Current.account = @current_account end def find_current_account @@ -29,11 +30,17 @@ class ApplicationController < ActionController::Base end def switch_locale(account) - I18n.locale = (I18n.available_locales.map(&:to_s).include?(account.locale) ? account.locale : nil) || I18n.default_locale + # priority is for locale set in query string (mostly for widget/from js sdk) + locale ||= (I18n.available_locales.map(&:to_s).include?(params[:locale]) ? params[:locale] : nil) + # if local is not set in param, lets try account + locale ||= (I18n.available_locales.map(&:to_s).include?(account.locale) ? account.locale : nil) + I18n.locale = locale || I18n.default_locale end def account_accessible_for_user?(account) - render_unauthorized('You are not authorized to access this account') unless account.account_users.find_by(user_id: current_user.id) + @current_account_user = account.account_users.find_by(user_id: current_user.id) + Current.account_user = @current_account_user + render_unauthorized('You are not authorized to access this account') unless @current_account_user end def account_accessible_for_bot?(account) @@ -98,4 +105,12 @@ class ApplicationController < ActionController::Base render json: { error: 'Account Suspended' }, status: :account_suspended end end + + def pundit_user + { + user: Current.user, + account: Current.account, + account_user: Current.account_user + } + end end diff --git a/app/controllers/concerns/access_token_auth_helper.rb b/app/controllers/concerns/access_token_auth_helper.rb index e7af9e116..3d6f55674 100644 --- a/app/controllers/concerns/access_token_auth_helper.rb +++ b/app/controllers/concerns/access_token_auth_helper.rb @@ -4,17 +4,25 @@ module AccessTokenAuthHelper 'api/v1/accounts/conversations/messages' => ['create'] }.freeze - def authenticate_access_token! + def ensure_access_token token = request.headers[:api_access_token] || request.headers[:HTTP_API_ACCESS_TOKEN] - access_token = AccessToken.find_by(token: token) - render_unauthorized('Invalid Access Token') && return unless access_token + @access_token = AccessToken.find_by(token: token) if token.present? + end - token_owner = access_token.owner - @resource = token_owner + def authenticate_access_token! + ensure_access_token + render_unauthorized('Invalid Access Token') && return if @access_token.blank? + + @resource = @access_token.owner + end + + def super_admin? + @resource.present? && @resource.is_a?(SuperAdmin) end def validate_bot_access_token! return if current_user.is_a?(User) + return if super_admin? return if agent_bot_accessible? render_unauthorized('Access to this endpoint is not authorized for bots') diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 407cf7d69..272c2f546 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -1,5 +1,21 @@ class DashboardController < ActionController::Base + before_action :set_global_config + layout 'vueapp' def index; end + + private + + def set_global_config + @global_config = GlobalConfig.get( + 'LOGO', + 'LOGO_THUMBNAIL', + 'INSTALLATION_NAME', + 'WIDGET_BRAND_URL', + 'TERMS_URL', + 'PRIVACY_URL', + 'DISPLAY_MANIFEST' + ) + end end diff --git a/app/controllers/devise_overrides/passwords_controller.rb b/app/controllers/devise_overrides/passwords_controller.rb index 4289d5af2..ed9d012eb 100644 --- a/app/controllers/devise_overrides/passwords_controller.rb +++ b/app/controllers/devise_overrides/passwords_controller.rb @@ -11,7 +11,7 @@ class DeviseOverrides::PasswordsController < Devise::PasswordsController @recoverable = User.find_by(reset_password_token: reset_password_token) if @recoverable && reset_password_and_confirmation(@recoverable) send_auth_headers(@recoverable) - render 'devise/auth.json', locals: { resource: @recoverable } + render partial: 'devise/auth.json', locals: { resource: @recoverable } else render json: { "message": 'Invalid token', "redirect_url": '/' }, status: 422 end diff --git a/app/controllers/devise_overrides/sessions_controller.rb b/app/controllers/devise_overrides/sessions_controller.rb index b9cec5447..9ebb3b435 100644 --- a/app/controllers/devise_overrides/sessions_controller.rb +++ b/app/controllers/devise_overrides/sessions_controller.rb @@ -4,6 +4,6 @@ class DeviseOverrides::SessionsController < ::DeviseTokenAuth::SessionsControlle wrap_parameters format: [] def render_create_success - render 'devise/auth.json', locals: { resource: @resource } + render partial: 'devise/auth.json', locals: { resource: @resource } end end diff --git a/app/controllers/super_admin/access_tokens_controller.rb b/app/controllers/super_admin/access_tokens_controller.rb new file mode 100644 index 000000000..a8a2669c4 --- /dev/null +++ b/app/controllers/super_admin/access_tokens_controller.rb @@ -0,0 +1,44 @@ +class SuperAdmin::AccessTokensController < SuperAdmin::ApplicationController + # Overwrite any of the RESTful controller actions to implement custom behavior + # For example, you may want to send an email after a foo is updated. + # + # def update + # super + # send_foo_updated_email(requested_resource) + # end + + # Override this method to specify custom lookup behavior. + # This will be used to set the resource for the `show`, `edit`, and `update` + # actions. + # + # def find_resource(param) + # Foo.find_by!(slug: param) + # end + + # The result of this lookup will be available as `requested_resource` + + # Override this if you have certain roles that require a subset + # this will be used to set the records shown on the `index` action. + # + # def scoped_resource + # if current_user.super_admin? + # resource_class + # else + # resource_class.with_less_stuff + # end + # end + + # Override `resource_params` if you want to transform the submitted + # data before it's persisted. For example, the following would turn all + # empty values into nil values. It uses other APIs such as `resource_class` + # and `dashboard`: + # + # def resource_params + # params.require(resource_class.model_name.param_key). + # permit(dashboard.permitted_attributes). + # transform_values { |value| value == "" ? nil : value } + # end + + # See https://administrate-prototype.herokuapp.com/customizing_controller_actions + # for more information +end diff --git a/app/controllers/super_admin/account_users_controller.rb b/app/controllers/super_admin/account_users_controller.rb new file mode 100644 index 000000000..b210dea19 --- /dev/null +++ b/app/controllers/super_admin/account_users_controller.rb @@ -0,0 +1,56 @@ +class SuperAdmin::AccountUsersController < SuperAdmin::ApplicationController + # Overwrite any of the RESTful controller actions to implement custom behavior + # For example, you may want to send an email after a foo is updated. + # + def create + resource = resource_class.new(resource_params) + authorize_resource(resource) + + notice = resource.save ? translate_with_resource('create.success') : resource.errors.full_messages.first + redirect_back(fallback_location: [namespace, resource.account], notice: notice) + end + + def destroy + if requested_resource.destroy + flash[:notice] = translate_with_resource('destroy.success') + else + flash[:error] = requested_resource.errors.full_messages.join('{{ title }}
@@ -8,7 +8,7 @@ {{ subTitle }}