diff --git a/Gemfile b/Gemfile index 5227a568..2441f32d 100644 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,7 @@ gem 'sprockets-rails' gem 'pg', '~> 1.4' # Use the Puma web server [https://github.com/puma/puma] -gem 'puma', '~> 6.0.1' +gem 'puma', '~> 6.1.0' # Returning to 5.6.5 as the 6.0 seems to have some issues slowing down requests # gem 'puma', '~>5.6.5' @@ -161,6 +161,12 @@ group :development do # Add support to Brakeman [https://github.com/presidentbeef/brakeman] # Vulnerability scanner gem 'brakeman' + + # Add Model annotations + gem 'annotate', '~>3.2.0' + + # Add Bullet to monitor and help fix N+1 DB queries + gem 'bullet' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 34acafa9..1713eeb9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,54 +1,54 @@ GIT remote: https://github.com/faker-ruby/faker.git - revision: e199b6dd4def391feac529f91a7bf2e2e3a9ea7f + revision: 53843386b662445d1d766f7b4226aa63549954a6 specs: - faker (3.1.0) + faker (3.1.1) i18n (>= 1.8.11, < 2) GEM remote: https://rubygems.org/ specs: - actioncable (7.0.4.1) - actionpack (= 7.0.4.1) - activesupport (= 7.0.4.1) + actioncable (7.0.4.3) + actionpack (= 7.0.4.3) + activesupport (= 7.0.4.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.4.1) - actionpack (= 7.0.4.1) - activejob (= 7.0.4.1) - activerecord (= 7.0.4.1) - activestorage (= 7.0.4.1) - activesupport (= 7.0.4.1) + actionmailbox (7.0.4.3) + actionpack (= 7.0.4.3) + activejob (= 7.0.4.3) + activerecord (= 7.0.4.3) + activestorage (= 7.0.4.3) + activesupport (= 7.0.4.3) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.4.1) - actionpack (= 7.0.4.1) - actionview (= 7.0.4.1) - activejob (= 7.0.4.1) - activesupport (= 7.0.4.1) + actionmailer (7.0.4.3) + actionpack (= 7.0.4.3) + actionview (= 7.0.4.3) + activejob (= 7.0.4.3) + activesupport (= 7.0.4.3) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.4.1) - actionview (= 7.0.4.1) - activesupport (= 7.0.4.1) + actionpack (7.0.4.3) + actionview (= 7.0.4.3) + activesupport (= 7.0.4.3) rack (~> 2.0, >= 2.2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.4.1) - actionpack (= 7.0.4.1) - activerecord (= 7.0.4.1) - activestorage (= 7.0.4.1) - activesupport (= 7.0.4.1) + actiontext (7.0.4.3) + actionpack (= 7.0.4.3) + activerecord (= 7.0.4.3) + activestorage (= 7.0.4.3) + activesupport (= 7.0.4.3) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.4.1) - activesupport (= 7.0.4.1) + actionview (7.0.4.3) + activesupport (= 7.0.4.3) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -58,33 +58,36 @@ GEM activemodel (>= 5.2.0) activestorage (>= 5.2.0) activesupport (>= 5.2.0) - activejob (7.0.4.1) - activesupport (= 7.0.4.1) + activejob (7.0.4.3) + activesupport (= 7.0.4.3) globalid (>= 0.3.6) - activemodel (7.0.4.1) - activesupport (= 7.0.4.1) - activerecord (7.0.4.1) - activemodel (= 7.0.4.1) - activesupport (= 7.0.4.1) - activestorage (7.0.4.1) - actionpack (= 7.0.4.1) - activejob (= 7.0.4.1) - activerecord (= 7.0.4.1) - activesupport (= 7.0.4.1) + activemodel (7.0.4.3) + activesupport (= 7.0.4.3) + activerecord (7.0.4.3) + activemodel (= 7.0.4.3) + activesupport (= 7.0.4.3) + activestorage (7.0.4.3) + actionpack (= 7.0.4.3) + activejob (= 7.0.4.3) + activerecord (= 7.0.4.3) + activesupport (= 7.0.4.3) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.4.1) + activesupport (7.0.4.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) addressable (2.8.1) public_suffix (>= 2.0.2, < 6.0) - ahoy_matey (4.1.0) + ahoy_matey (4.2.1) activesupport (>= 5.2) device_detector safely_block (>= 0.2.1) - appmap (0.95.0) + annotate (3.2.0) + activerecord (>= 3.2, < 8.0) + rake (>= 10.4, < 14.0) + appmap (0.98.1) activesupport method_source rack @@ -94,26 +97,29 @@ GEM ffi-compiler (~> 1.0) ast (2.4.2) aws-eventstream (1.2.0) - aws-partitions (1.695.0) - aws-sdk-core (3.169.0) + aws-partitions (1.730.0) + aws-sdk-core (3.170.1) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.62.0) + aws-sdk-kms (1.63.0) aws-sdk-core (~> 3, >= 3.165.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.118.0) + aws-sdk-s3 (1.119.1) aws-sdk-core (~> 3, >= 3.165.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) aws-sigv4 (1.5.2) aws-eventstream (~> 1, >= 1.0.2) bindex (0.8.1) - bootsnap (1.15.0) + bootsnap (1.16.0) msgpack (~> 1.2) - brakeman (5.4.0) + brakeman (5.4.1) builder (3.2.4) + bullet (7.0.7) + activesupport (>= 3.0.0) + uniform_notifier (~> 1.11) capybara (3.38.0) addressable matrix @@ -131,11 +137,11 @@ GEM caxlsx_rails (0.6.3) actionpack (>= 3.1) caxlsx (>= 3.0) - chartkick (5.0.0) + chartkick (5.0.1) chunky_png (1.4.0) - concurrent-ruby (1.1.10) + concurrent-ruby (1.2.2) connection_pool (2.3.0) - countries (5.3.0) + countries (5.3.1) unaccent (~> 0.3) crass (1.0.6) cssbundling-rails (1.1.2) @@ -151,7 +157,7 @@ GEM addressable (~> 2.8) errbase (0.2.2) erubi (1.12.0) - faraday (2.7.3) + faraday (2.7.4) faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) faraday-net_http (3.0.2) @@ -159,12 +165,12 @@ GEM ffi-compiler (1.0.1) ffi (>= 1.0.0) rake - globalid (1.0.1) + globalid (1.1.0) activesupport (>= 5.0) - groupdate (6.1.0) + groupdate (6.2.0) activesupport (>= 5.2) hashie (5.0.0) - honeybadger (5.0.2) + honeybadger (5.2.1) htmlentities (4.3.4) http (5.1.1) addressable (~> 2.8) @@ -180,7 +186,7 @@ GEM mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) io-console (0.6.0) - irb (1.6.2) + irb (1.6.3) reline (>= 0.3.0) jbuilder (2.11.5) actionview (>= 5.0.0) @@ -189,14 +195,14 @@ GEM jsbundling-rails (1.1.1) railties (>= 6.0.0) json (2.6.3) - jwt (2.6.0) + jwt (2.7.0) llhttp-ffi (0.4.0) ffi-compiler (~> 1.0) rake (~> 13.0) loofah (2.19.1) crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.8.0.1) + mail (2.8.1) mini_mime (>= 0.1.1) net-imap net-pop @@ -206,8 +212,8 @@ GEM method_source (1.0.0) mini_magick (4.12.0) mini_mime (1.1.2) - minitest (5.17.0) - msgpack (1.6.0) + minitest (5.18.0) + msgpack (1.6.1) multi_xml (0.6.0) net-imap (0.3.4) date @@ -219,9 +225,7 @@ GEM net-smtp (0.3.3) net-protocol nio4r (2.5.8) - nokogiri (1.14.0-arm64-darwin) - racc (~> 1.4) - nokogiri (1.14.0-x86_64-linux) + nokogiri (1.14.2-arm64-darwin) racc (~> 1.4) noticed (1.6.0) http (>= 4.0.0) @@ -233,7 +237,7 @@ GEM rack (>= 1.2, < 4) snaky_hash (~> 2.0) version_gem (~> 1.1) - omniauth (2.1.0) + omniauth (2.1.1) hashie (>= 3.4.6) rack (>= 2.2.3) rack-protection @@ -248,47 +252,47 @@ GEM omniauth-rails_csrf_protection (1.0.1) actionpack (>= 4.2) omniauth (~> 2.0) - pagy (6.0.1) + pagy (6.0.2) paper_trail (14.0.0) activerecord (>= 6.0) request_store (~> 1.4) parallel (1.22.1) - parser (3.2.0.0) + parser (3.2.1.1) ast (~> 2.4.1) - pg (1.4.5) + pg (1.4.6) public_suffix (5.0.1) - puma (6.0.2) + puma (6.1.1) nio4r (~> 2.0) pundit (2.3.0) activesupport (>= 3.0.0) racc (1.6.2) - rack (2.2.6.2) + rack (2.2.6.4) rack-protection (3.0.5) rack - rack-test (2.0.2) + rack-test (2.1.0) rack (>= 1.3) - rails (7.0.4.1) - actioncable (= 7.0.4.1) - actionmailbox (= 7.0.4.1) - actionmailer (= 7.0.4.1) - actionpack (= 7.0.4.1) - actiontext (= 7.0.4.1) - actionview (= 7.0.4.1) - activejob (= 7.0.4.1) - activemodel (= 7.0.4.1) - activerecord (= 7.0.4.1) - activestorage (= 7.0.4.1) - activesupport (= 7.0.4.1) + rails (7.0.4.3) + actioncable (= 7.0.4.3) + actionmailbox (= 7.0.4.3) + actionmailer (= 7.0.4.3) + actionpack (= 7.0.4.3) + actiontext (= 7.0.4.3) + actionview (= 7.0.4.3) + activejob (= 7.0.4.3) + activemodel (= 7.0.4.3) + activerecord (= 7.0.4.3) + activestorage (= 7.0.4.3) + activesupport (= 7.0.4.3) bundler (>= 1.15.0) - railties (= 7.0.4.1) + railties (= 7.0.4.3) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.4.4) + rails-html-sanitizer (1.5.0) loofah (~> 2.19, >= 2.19.1) - railties (7.0.4.1) - actionpack (= 7.0.4.1) - activesupport (= 7.0.4.1) + railties (7.0.4.3) + actionpack (= 7.0.4.3) + activesupport (= 7.0.4.3) method_source rake (>= 12.2) thor (~> 1.0) @@ -297,9 +301,9 @@ GEM rake (13.0.6) redis (5.0.6) redis-client (>= 0.9.0) - redis-client (0.12.1) + redis-client (0.14.0) connection_pool - regexp_parser (2.6.1) + regexp_parser (2.7.0) reline (0.3.2) io-console (~> 0.5) request_store (1.5.1) @@ -307,28 +311,28 @@ GEM reverse_markdown (2.1.1) nokogiri rexml (3.2.5) - rolify (6.0.0) + rolify (6.0.1) rqrcode (2.1.2) chunky_png (~> 1.0) rqrcode_core (~> 1.0) rqrcode_core (1.2.0) - rubocop (1.43.0) + rubocop (1.48.1) json (~> 2.3) parallel (~> 1.10) parser (>= 3.2.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.24.1, < 2.0) + rubocop-ast (>= 1.26.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.24.1) - parser (>= 3.1.1.0) - rubocop-rails (2.17.4) + rubocop-ast (1.27.0) + parser (>= 3.2.1.0) + rubocop-rails (2.18.0) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) - ruby-progressbar (1.11.0) + ruby-progressbar (1.13.0) ruby-vips (2.1.4) ffi (~> 1.12) ruby2_keywords (0.0.5) @@ -336,7 +340,7 @@ GEM rubyzip (2.3.2) safely_block (0.3.0) errbase (>= 0.1.1) - selenium-webdriver (4.7.1) + selenium-webdriver (4.8.1) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) @@ -345,7 +349,7 @@ GEM sendgrid-ruby (~> 6.4) sendgrid-ruby (6.6.2) ruby_http_client (~> 3.4) - sidekiq (7.0.3) + sidekiq (7.0.7) concurrent-ruby (< 2) connection_pool (>= 2.3.0) rack (>= 2.2.4) @@ -363,22 +367,23 @@ GEM stimulus-rails (1.2.1) railties (>= 6.0.0) thor (1.2.1) - timeout (0.3.1) - turbo-rails (1.3.2) + timeout (0.3.2) + turbo-rails (1.4.0) actionpack (>= 6.0.0) activejob (>= 6.0.0) railties (>= 6.0.0) - tzinfo (2.0.5) + tzinfo (2.0.6) concurrent-ruby (~> 1.0) unaccent (0.4.0) unf (0.1.4) unf_ext unf_ext (0.0.8.2) unicode-display_width (2.4.2) + uniform_notifier (1.16.0) validate_url (1.0.15) activemodel (>= 3.0.0) public_suffix - version_gem (1.1.1) + version_gem (1.1.2) view_component (2.82.0) activesupport (>= 5.2.0, < 8.0) concurrent-ruby (~> 1.0) @@ -398,20 +403,21 @@ GEM websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.6) + zeitwerk (2.6.7) PLATFORMS arm64-darwin-21 - x86_64-linux DEPENDENCIES active_storage_validations (~> 1.0.0) ahoy_matey + annotate (~> 3.2.0) appmap argon2 (~> 2.2.0) aws-sdk-s3 bootsnap brakeman + bullet capybara caxlsx caxlsx_rails @@ -434,7 +440,7 @@ DEPENDENCIES pagy (~> 6.0.0) paper_trail pg (~> 1.4) - puma (~> 6.0.1) + puma (~> 6.1.0) pundit rails (~> 7.0.4) redis (~> 5.0.5) @@ -459,4 +465,4 @@ RUBY VERSION ruby 3.1.2p20 BUNDLED WITH - 2.4.4 + 2.4.8 diff --git a/app/assets/stylesheets/actiontext.css b/app/assets/stylesheets/actiontext.css index 3cfcb2b7..77b43ec0 100644 --- a/app/assets/stylesheets/actiontext.css +++ b/app/assets/stylesheets/actiontext.css @@ -29,3 +29,8 @@ padding: 0 !important; max-width: 100% !important; } + +/* monkey patch to have nice button in light and dark modes */ +.trix-button { + background: #ddd !important; +} \ No newline at end of file diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css index 990a9531..09e56693 100644 --- a/app/assets/stylesheets/application.tailwind.css +++ b/app/assets/stylesheets/application.tailwind.css @@ -1,10 +1,10 @@ +@import "actiontext"; + /* Tailwind CSS */ @tailwind base; @tailwind components; @tailwind utilities; -@import 'actiontext.css'; - /* Custom animation used by Alert messages (aka Flash) */ @keyframes notification-countdown { from { @@ -14,4 +14,7 @@ to { width: 0; } -} \ No newline at end of file +} + +/* Applying style to Trix using Tailwind Typograhy plugin */ +trix-editor { @apply prose max-w-none } diff --git a/app/components/dashboard_counter_component.html.erb b/app/components/dashboard_counter_component.html.erb new file mode 100644 index 00000000..165be7a2 --- /dev/null +++ b/app/components/dashboard_counter_component.html.erb @@ -0,0 +1,31 @@ +
+
+

<%= @title %>

+
+ <% if @link %> + <%= link_to @link do %> + <%= @icon.html_safe %> + <% end %> + <% else %> + <%= @icon.html_safe %> + <% end %> +
+
+
+ <%= @total %> +
+
+
+
Negative
+

<%= @negative %>%

+
+
+
Neutral
+

<%= @neutral %>%

+
+
+
Positive
+

<%= @positive %>%

+
+
+
diff --git a/app/components/dashboard_counter_component.rb b/app/components/dashboard_counter_component.rb new file mode 100644 index 00000000..3351ed7d --- /dev/null +++ b/app/components/dashboard_counter_component.rb @@ -0,0 +1,41 @@ +# app/components/dashboard_counter_component.rb + +# frozen_string_literal: true + +# @param title [String] +# @param total [Integer] +# @param icon [Integer] +# @param icon_color [Integer] +# @param link [String] +# @param positive [Integer] +# @param neutral [Integer] +# @param negative [Integer] +# @param background_color [String] + +# Examples +# DashboardCounterComponent.new() +# DashboardCounterComponent.new() +class DashboardCounterComponent < ViewComponent::Base + def initialize(title:, total:, icon:, icon_color:, link:nil, positive:0, neutral:0, negative:0, background_color:) + + @title = title.capitalize + @total = total # Sum of positive, negative, neutral must be equal to the total + @icon = icon # Icon to be displayed next to the title (svg format) + @icon_color = icon_color # Tailwind color passed as text-{color}-{value} + @link = link # In case the icon should open a new page + @positive = compute_percentage(total, positive) # % of positive questions from the total + @neutral = compute_percentage(total, neutral) # % of neutral questions from the total + @negative = compute_percentage(total, negative) # % of negative questions from the total + @background_color = background_color # Tailwind color passed as bg-{color}-{value} + + end + + private + + def compute_percentage(total, value) + if total > 0 + ((value.to_f / total) * 100).round + end + end + +end diff --git a/app/components/dashboard_question_table_component.html.erb b/app/components/dashboard_question_table_component.html.erb new file mode 100644 index 00000000..a53cfcbb --- /dev/null +++ b/app/components/dashboard_question_table_component.html.erb @@ -0,0 +1,10 @@ +
+ + <%= header %> + + <% questions.each do |question| %> + <%= question %> + <% end %> + +
+
diff --git a/app/components/dashboard_question_table_component.rb b/app/components/dashboard_question_table_component.rb new file mode 100644 index 00000000..63f1490a --- /dev/null +++ b/app/components/dashboard_question_table_component.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class DashboardQuestionTableComponent < ViewComponent::Base + renders_one :header + renders_many :questions + +end diff --git a/app/controllers/concerns/domain_validation.rb b/app/controllers/concerns/domain_validation.rb new file mode 100644 index 00000000..77dbe429 --- /dev/null +++ b/app/controllers/concerns/domain_validation.rb @@ -0,0 +1,34 @@ +# app/controllers/concerns/domain_validation.rb +module DomainValidation + extend ActiveSupport::Concern + + VERIFICATION_STRING = "thepew-domain-verification=".freeze + + # Method used to verify that the TXT entry saved in the Organizations table is + # properly attached to the TXT record of the DNS for that domain + # domain is passed as a String + # return true when a TXT record with the DNS TXT value is found in the DNS servers + # return false is no TXT record is found in the DNS servers + def valid? domain + require "resolv" + dns_obj = Resolv::DNS.new( :nameserver => ['8.8.8.8', '8.8.4.4'] ) + + resp = dns_obj.getresources domain, Resolv::DNS::Resource::IN::TXT + resp_array = resp.map { |r| r.data.to_s } + + dns_txt = Organization.where(domain: domain).select(:dns_txt).first + + if dns_txt.nil? + # Return false when there is no dns txt value in the datbase and report the error + logger.error "No DNS TXT for ${domain}" + return false + else + # Store the verification string into value and look for it in the TXT entries returned by the DNS server + value = VERIFICATION_STRING + dns_txt + + # Return true if value was in the array, false otherwise + return resp_array.include? value + end + + end +end \ No newline at end of file diff --git a/app/controllers/dashboards_controller.rb b/app/controllers/dashboards_controller.rb index 52c6d9ba..11d50a3d 100644 --- a/app/controllers/dashboards_controller.rb +++ b/app/controllers/dashboards_controller.rb @@ -1,4 +1,28 @@ class DashboardsController < ApplicationController - def index + before_action :authenticate_user! + before_action :redirect_if_unauthenticated + + def show + @questions = current_user.questions.order(created_at: :desc).limit(5) + @status = current_user.questions.group(:status).count + @total = @status.values.reduce(:+) # Sum all the questions + @pew_points = 0 + + # Call for @pew_points update + update_pew_points end + + private + + # TODO: move it to a scheduled task or a counting cache + def update_pew_points + @questions.each do |question| + @votes = question.votes + if @votes.count > 0 + c = @votes.group(:choice).count + @pew_points = @pew_points + c['up_vote'] + end + end + end + end diff --git a/app/controllers/organization_controller.rb b/app/controllers/organization_controller.rb index 8413a0a3..befb4812 100644 --- a/app/controllers/organization_controller.rb +++ b/app/controllers/organization_controller.rb @@ -3,6 +3,7 @@ class OrganizationController < ApplicationController before_action :authenticate_user! before_action :redirect_if_unauthenticated + before_action :set_organization # DELETE /organization/:id def destroy @@ -13,21 +14,35 @@ def destroy def edit # TODO add a condition for when a user is an admin for the account. # Current code only displays account information when the user is the owner - @account = Account.where(account_id: Member.where(user_id: current_user.id, owner: true).first.id).first - @account_owner = true + @organization.name = nil if @organization.name === "__default__" end # GET /organization/:id + # TODO make it API only as the app is using the edit form def show - @account = Account.find(params[:id]) - # Shall be moved to another controller that only deals with the users who are part of an account - # @account_users = User.where(user_id: User.where(user_id: Member.where(account_id: @account.id)).select(:user_id)) + # @account = Account.find(params[:id]) end # PUT /organization/:id def update + + if ((update_organization_params[:name].nil? || update_organization_params[:name].strip.length == 0) && @organization.name.nil?) + @organization.name = "__default__" + end + if @organization.update(update_organization_params) + render :edit, status: :ok + else + render :edit, status: :unprocessable_entity + end end private + def update_organization_params + params.require(:organization).permit(:name, :website, :description, :logo) + end + + def set_organization + @organization = Organization.find(params[:id]) + end end diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb index 76acddca..c1edcef9 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings_controller.rb @@ -8,7 +8,7 @@ class SettingsController < ApplicationController def index # TODO add a condition for when a user is an admin for the account. # Current code only displays account information when the user is the owner - @account_id = Member.where(user_id: current_user.id, owner: true).first.account_id - @account_owner = true + @organization_id = Member.where(user_id: current_user.id, owner: true).first.organization_id + @organization_owner = true end end diff --git a/app/controllers/ssos_controller.rb b/app/controllers/ssos_controller.rb new file mode 100644 index 00000000..568e9b39 --- /dev/null +++ b/app/controllers/ssos_controller.rb @@ -0,0 +1,29 @@ +class SsosController < ApplicationController + before_action :authenticate_user! + before_action :redirect_if_unauthenticated + before_action :set_organization + + def edit; end + + def show + end + + def update + if @organization.update(update_sso_params) + render :edit, status: :ok + else + render :edit, status: :unprocessable_entity + end + end + + private + + def update_sso_params + params.require(:organization).permit(:domain, :sso) + end + + def set_organization + @organization = Organization.find(params[:organization_id]) + @organization.name = nil if @organization.name === "__default__" + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index a8cf27bd..36364eaa 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,7 +1,13 @@ class UsersController < ApplicationController - before_action :authenticate_user!, only: %i[edit destroy update] + before_action :authenticate_user!, only: %i[edit destroy update index] before_action :redirect_if_authenticated, only: %i[create new] + # GET /organization/:id/users + def index + @organization = Organization.find(params[:organization_id]) + @users = @organization.users + end + def create @user = User.new(create_user_params) if @user.save diff --git a/app/controllers/your_questions_controller.rb b/app/controllers/your_questions_controller.rb index f7bff426..18b73a63 100644 --- a/app/controllers/your_questions_controller.rb +++ b/app/controllers/your_questions_controller.rb @@ -5,8 +5,12 @@ class YourQuestionsController < ApplicationController def index # TODO: make sure the user is the only one enable to read the question # Pundit ;-) - @questions = Question.where(user_id: current_user.id).order(created_at: :desc) + @questions = current_user.questions.order(created_at: :desc) @count = @questions.count end + def show + @question = current_user.questions.find(params[:id]) + end + end diff --git a/app/helpers/questions_helper.rb b/app/helpers/questions_helper.rb index 88b9c817..00993489 100644 --- a/app/helpers/questions_helper.rb +++ b/app/helpers/questions_helper.rb @@ -1,4 +1,6 @@ module QuestionsHelper + # Used in _question.html.erb and _sub_question.html.erb + # Display the question status def question_status(question) case question.status when 'asked' @@ -17,6 +19,25 @@ def question_status(question) end end + # Used in your_questions#show, your_question#index, dahsboard#show + # Display the question status in a flowbite bordered badge + def display_question_status(status) + case status + when 'approved' + content_tag(:span, "Approved" , class: "bg-blue-100 text-blue-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-blue-400 border border-blue-400") + when 'asked' + content_tag(:span, "Pending" , class: "bg-yellow-100 text-yellow-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-yellow-300 border border-yellow-300") + when 'beinganswered' + content_tag(:span, "Being Answered" , class: "bg-green-100 text-green-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400") + when 'answered' + content_tag(:span, "Ansered" , class: "bg-green-100 text-green-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400") + when 'rejected' + content_tag(:span, "Rejected" , class: "bg-red-100 text-red-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400") + else + + end + end + private def rejection_cause(question) diff --git a/app/helpers/ssos_helper.rb b/app/helpers/ssos_helper.rb new file mode 100644 index 00000000..5dc64648 --- /dev/null +++ b/app/helpers/ssos_helper.rb @@ -0,0 +1,2 @@ +module SsosHelper +end diff --git a/app/javascript/application.js b/app/javascript/application.js index e0e5671a..8974c8bf 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -7,5 +7,5 @@ import "chartkick/chart.js"; // document.addEventListener("turbo:load", function () { // console.log("Ready triggered!"); // }); -import "trix" -import "@rails/actiontext" +import "trix"; +import "@rails/actiontext"; diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index 3533b688..4ba56e94 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -34,6 +34,9 @@ application.register("reset-form", ResetFormController) import ToggleController from "./toggle_controller" application.register("toggle", ToggleController) +import TurboModalController from "./turbo_modal_controller" +application.register("turbo-modal", TurboModalController) + import UserVoteController from "./user_vote_controller" application.register("user-vote", UserVoteController) diff --git a/app/javascript/controllers/turbo_modal_controller.js b/app/javascript/controllers/turbo_modal_controller.js new file mode 100644 index 00000000..a92940bb --- /dev/null +++ b/app/javascript/controllers/turbo_modal_controller.js @@ -0,0 +1,17 @@ +import { Controller } from "@hotwired/stimulus"; + +// Connects to data-controller="turbo-modal" +export default class extends Controller { + // hide modal on successful form submission + // action: "turbo:submit-end->turbo-modal#submitEnd" + submitEnd(e) { + if (e.detail.success) { + this.hideModal(); + } + } + + hideModal() { + this.element.parentElement.removeAttribute("src"); + this.element.remove(); + } +} diff --git a/app/models/active_session.rb b/app/models/active_session.rb index 6ad197cd..b34ad97e 100644 --- a/app/models/active_session.rb +++ b/app/models/active_session.rb @@ -1,3 +1,26 @@ +# == Schema Information +# +# Table name: active_sessions +# +# id :uuid not null, primary key +# ip_address :string +# remember_token :string not null +# user_agent :string +# created_at :datetime not null +# updated_at :datetime not null +# user_id :uuid not null +# +# Indexes +# +# index_active_sessions_on_ip_address (ip_address) +# index_active_sessions_on_remember_token (remember_token) UNIQUE +# index_active_sessions_on_user_agent (user_agent) +# index_active_sessions_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (user_id => users.id) ON DELETE => cascade +# class ActiveSession < ApplicationRecord belongs_to :user diff --git a/app/models/ahoy/event.rb b/app/models/ahoy/event.rb index 4c3125b5..0dee063e 100644 --- a/app/models/ahoy/event.rb +++ b/app/models/ahoy/event.rb @@ -1,3 +1,21 @@ +# == Schema Information +# +# Table name: ahoy_events +# +# id :bigint not null, primary key +# name :string +# properties :jsonb +# time :datetime +# user_id :bigint +# visit_id :bigint +# +# Indexes +# +# index_ahoy_events_on_name_and_time (name,time) +# index_ahoy_events_on_properties (properties) USING gin +# index_ahoy_events_on_user_id (user_id) +# index_ahoy_events_on_visit_id (visit_id) +# class Ahoy::Event < ApplicationRecord include Ahoy::QueryMethods diff --git a/app/models/ahoy/visit.rb b/app/models/ahoy/visit.rb index 66f89e5d..9f496b26 100644 --- a/app/models/ahoy/visit.rb +++ b/app/models/ahoy/visit.rb @@ -1,3 +1,39 @@ +# == Schema Information +# +# Table name: ahoy_visits +# +# id :bigint not null, primary key +# app_version :string +# browser :string +# city :string +# country :string +# device_type :string +# ip :string +# landing_page :text +# latitude :float +# longitude :float +# os :string +# os_version :string +# platform :string +# referrer :text +# referring_domain :string +# region :string +# started_at :datetime +# user_agent :text +# utm_campaign :string +# utm_content :string +# utm_medium :string +# utm_source :string +# utm_term :string +# visit_token :string +# visitor_token :string +# user_id :bigint +# +# Indexes +# +# index_ahoy_visits_on_user_id (user_id) +# index_ahoy_visits_on_visit_token (visit_token) UNIQUE +# class Ahoy::Visit < ApplicationRecord self.table_name = "ahoy_visits" diff --git a/app/models/attendance.rb b/app/models/attendance.rb index 2ff81f8b..f9b9e615 100644 --- a/app/models/attendance.rb +++ b/app/models/attendance.rb @@ -1,3 +1,28 @@ +# == Schema Information +# +# Table name: attendances +# +# id :uuid not null, primary key +# end_time :datetime +# start_time :datetime not null +# status :integer default("offline"), not null +# created_at :datetime not null +# updated_at :datetime not null +# event_id :uuid not null +# room_id :bigint +# user_id :uuid not null +# +# Indexes +# +# index_attendances_on_event_id (event_id) +# index_attendances_on_room_id (room_id) +# index_attendances_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (event_id => events.id) +# fk_rails_... (user_id => users.id) +# class Attendance < ApplicationRecord belongs_to :user belongs_to :event diff --git a/app/models/event.rb b/app/models/event.rb index ce414dee..a28378c9 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -1,3 +1,36 @@ +# == Schema Information +# +# Table name: events +# +# id :uuid not null, primary key +# allow_anonymous :boolean default(FALSE), not null +# always_on :boolean default(FALSE), not null +# duration :integer +# end_date :datetime not null +# event_type :integer not null +# name :string not null +# short_code :string +# start_date :datetime not null +# status :integer default("draft"), not null +# created_at :datetime not null +# updated_at :datetime not null +# organization_id :uuid not null +# user_id :uuid not null +# +# Indexes +# +# index_events_on_allow_anonymous (allow_anonymous) +# index_events_on_always_on (always_on) +# index_events_on_event_type (event_type) +# index_events_on_organization_id (organization_id) +# index_events_on_short_code (short_code) +# index_events_on_status (status) +# index_events_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (user_id => users.id) +# class Event < ApplicationRecord include Rails.application.routes.url_helpers @@ -32,7 +65,7 @@ class Event < ApplicationRecord def set_values self.end_date = start_date self.short_code = generate_pin if self.short_code.nil? - self.account_id = Member.where(user_id: self.user_id).first.account_id if self.account_id.nil? + self.organization_id = Member.where(user_id: self.user_id).first.organization_id if self.organization_id.nil? set_duration diff --git a/app/models/member.rb b/app/models/member.rb index d9926248..38684935 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -1,7 +1,24 @@ +# == Schema Information +# +# Table name: members +# +# id :uuid not null, primary key +# owner :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# organization_id :uuid not null +# user_id :uuid not null +# +# Indexes +# +# index_members_on_organization_id (organization_id) +# index_members_on_owner (owner) +# index_members_on_user_id (user_id) +# class Member < ApplicationRecord # Tracking changes has_paper_trail belongs_to :user - belongs_to :account + belongs_to :organization end diff --git a/app/models/message.rb b/app/models/message.rb index 3a1a6113..7548e84e 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -1,3 +1,24 @@ +# == Schema Information +# +# Table name: messages +# +# id :uuid not null, primary key +# content :text +# level :integer default("info"), not null +# title :string +# created_at :datetime not null +# updated_at :datetime not null +# user_id :uuid not null +# +# Indexes +# +# index_messages_on_level (level) +# index_messages_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (user_id => users.id) +# class Message < ApplicationRecord has_noticed_notifications diff --git a/app/models/notification.rb b/app/models/notification.rb index 168595df..5554c47b 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -1,3 +1,21 @@ +# == Schema Information +# +# Table name: notifications +# +# id :uuid not null, primary key +# params :jsonb +# read_at :datetime +# recipient_type :string not null +# type :string not null +# created_at :datetime not null +# updated_at :datetime not null +# recipient_id :bigint not null +# +# Indexes +# +# index_notifications_on_read_at (read_at) +# index_notifications_on_recipient (recipient_type,recipient_id) +# class Notification < ApplicationRecord include Noticed::Model belongs_to :recipient, polymorphic: true diff --git a/app/models/organization.rb b/app/models/organization.rb new file mode 100644 index 00000000..497262d5 --- /dev/null +++ b/app/models/organization.rb @@ -0,0 +1,85 @@ +# == Schema Information +# +# Table name: organizations +# +# id :uuid not null, primary key +# country :string +# dns_txt :string +# domain :string +# domain_verified :boolean default(FALSE), not null +# domain_verified_at :datetime +# name :string +# sso :boolean default(FALSE), not null +# website :string +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_organizations_on_country (country) +# index_organizations_on_dns_txt (dns_txt) UNIQUE +# index_organizations_on_domain (domain) UNIQUE +# index_organizations_on_domain_verified (domain_verified) +# index_organizations_on_sso (sso) +# +class Organization < ApplicationRecord + # enable rolify on the Account class + resourcify + + # Tracking changes + has_paper_trail + + # Callbacks + before_validation :generate_dns_txt, if: :will_save_change_to_domain? + before_validation :clean_domain, if: :will_save_change_to_domain? + + has_many :members + has_many :users, through: :members + + has_one_attached :logo + + has_rich_text :description + + validates :website, url: { allow_nil: true, schemes: ['https'] } + validates :name, presence: true, length: { minimum: 3, maximum: 120 } + validates :logo, content_type: ['image/png', 'image/jpeg', 'image/jpg', 'image/gif'], + size: { between: 1.kilobyte..5.megabytes, message: 'is not given between size' } + + # SSO related field + validates :domain, uniqueness: true, + allow_nil: true, + fully_qualified_domain: true, + length: { minimum: 3, maximum: 120 } + validates :dns_txt, uniqueness: true, length: { is: 126 }, allow_nil: true + + # Display the full dns text + # This includes the prefix (27 characters) and the unique 126 character string stored in the database + def full_dns_txt + prefix = "thepew-domain-verification=" # 27 characters + prefix + self.dns_txt + end + + private + + # Generate a unique TXT entry + def generate_dns_txt + self.dns_txt = random_unique_string if self.domain_changed? + end + + # Generates a unique 126 character long and case sensitive string + def random_unique_string + rus = "" + loop do + rus = SecureRandom.hex(63) # or whatever you chose like UUID tools + break unless self.class.exists?(dns_txt: rus) + end + rus + end + + # This method removes http and https://, potential trailing / and carriage returns, line feeds, spaces + # from the domain name + # It is called before validating the model and only if the domain name has changed + def clean_domain + self.domain = self.domain.gsub(/(http|https):\/\/|\/$/, '').gsub(/[\r\n\s]/, '') + end +end diff --git a/app/models/profile.rb b/app/models/profile.rb index 94451bd5..1b68c2d1 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -1,3 +1,23 @@ +# == Schema Information +# +# Table name: profiles +# +# id :uuid not null, primary key +# mode :integer default("light"), not null +# nickname :string +# created_at :datetime not null +# updated_at :datetime not null +# user_id :uuid not null +# +# Indexes +# +# index_profiles_on_mode (mode) +# index_profiles_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (user_id => users.id) +# class Profile < ApplicationRecord belongs_to :user diff --git a/app/models/question.rb b/app/models/question.rb index dc2ead0d..7bb341c3 100644 --- a/app/models/question.rb +++ b/app/models/question.rb @@ -1,3 +1,38 @@ +# == Schema Information +# +# Table name: questions +# +# id :uuid not null, primary key +# anonymous :boolean default(FALSE), not null +# rejection_cause :integer +# status :integer default("asked"), not null +# title :string not null +# tone :integer default("undefined"), not null +# created_at :datetime not null +# updated_at :datetime not null +# organization_id :uuid not null +# parent_id :uuid +# room_id :uuid not null +# user_id :uuid not null +# +# Indexes +# +# index_questions_on_anonymous (anonymous) +# index_questions_on_organization_id (organization_id) +# index_questions_on_parent_id (parent_id) +# index_questions_on_rejection_cause (rejection_cause) +# index_questions_on_room_id (room_id) +# index_questions_on_status (status) +# index_questions_on_tone (tone) +# index_questions_on_user_id (user_id) +# index_questions_on_user_id_and_room_id (user_id,room_id) +# index_questions_on_user_id_and_status (user_id,status) +# +# Foreign Keys +# +# fk_rails_... (room_id => rooms.id) +# fk_rails_... (user_id => users.id) +# class Question < ApplicationRecord # Enable rolify on the Question class resourcify @@ -9,7 +44,7 @@ class Question < ApplicationRecord has_paper_trail # Set the account_id (value is taken from the event) - before_validation :set_account_id + before_validation :set_organization_id belongs_to :user belongs_to :room @@ -38,6 +73,13 @@ class Question < ApplicationRecord other: 50 } + enum tone: { + positive: 30, + neutral: 20, + negative: 10, + undefined: 0 + } + scope :questions_for_room, -> (room) { where('room_id = ? AND parent_id IS NULL', room) } scope :approved_questions_for_room, -> (room) { where('room_id = ?', room).approved.or(where('room_id = ?', room).answered).or(where('room_id = ?', room).beinganswered) } scope :asked_questions_for_room, -> (room) { where('room_id = ?', room).asked } @@ -94,8 +136,8 @@ def email private - def set_account_id - self.account_id = self.room.account_id if self.account_id.nil? + def set_organization_id + self.organization_id = self.room.organization_id if self.organization_id.nil? end end diff --git a/app/models/role.rb b/app/models/role.rb index 29e7592c..e3977832 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -1,3 +1,19 @@ +# == Schema Information +# +# Table name: roles +# +# id :bigint not null, primary key +# name :string +# resource_type :string +# created_at :datetime not null +# updated_at :datetime not null +# resource_id :uuid +# +# Indexes +# +# index_roles_on_name_and_resource_type_and_resource_id (name,resource_type,resource_id) +# index_roles_on_resource (resource_type,resource_id) +# class Role < ApplicationRecord has_and_belongs_to_many :users, :join_table => :users_roles diff --git a/app/models/room.rb b/app/models/room.rb index 19d9272a..65cee7fa 100644 --- a/app/models/room.rb +++ b/app/models/room.rb @@ -1,3 +1,29 @@ +# == Schema Information +# +# Table name: rooms +# +# id :uuid not null, primary key +# allow_anonymous :boolean default(FALSE), not null +# always_on :boolean default(FALSE), not null +# name :string not null +# start_date :datetime not null +# created_at :datetime not null +# updated_at :datetime not null +# event_id :uuid not null +# organization_id :uuid not null +# +# Indexes +# +# index_rooms_on_allow_anonymous (allow_anonymous) +# index_rooms_on_always_on (always_on) +# index_rooms_on_event_id (event_id) +# index_rooms_on_organization_id (organization_id) +# index_rooms_on_start_date (start_date) +# +# Foreign Keys +# +# fk_rails_... (event_id => events.id) +# class Room < ApplicationRecord # enable rolify on the Room class resourcify @@ -6,7 +32,7 @@ class Room < ApplicationRecord has_paper_trail # Set the account_id (value is taken from the event) - before_validation :set_account_id + before_validation :set_organization_id belongs_to :event has_many :attendances, dependent: :destroy @@ -26,7 +52,7 @@ def asked_question_count private - def set_account_id - self.account_id = self.event.account_id if self.account_id.nil? + def set_organization_id + self.organization_id = self.event.organization_id if self.organization_id.nil? end end diff --git a/app/models/topic.rb b/app/models/topic.rb new file mode 100644 index 00000000..05bfca75 --- /dev/null +++ b/app/models/topic.rb @@ -0,0 +1,24 @@ +# == Schema Information +# +# Table name: topics +# +# id :uuid not null, primary key +# description :text +# name :string not null +# created_at :datetime not null +# updated_at :datetime not null +# event_id :uuid +# question_id :uuid +# room_id :uuid +# user_id :uuid +# +# Indexes +# +# index_topics_on_event_id (event_id) +# index_topics_on_name (name) +# index_topics_on_question_id (question_id) +# index_topics_on_room_id (room_id) +# index_topics_on_user_id (user_id) +# +class Topic < ApplicationRecord +end diff --git a/app/models/user.rb b/app/models/user.rb index d2ba6b05..046f4833 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,3 +1,29 @@ +# == Schema Information +# +# Table name: users +# +# id :uuid not null, primary key +# blocked :boolean default(FALSE), not null +# confirmed :boolean default(FALSE), not null +# confirmed_at :datetime +# email :string not null +# failed_attempts :integer default(0), not null +# locked :boolean default(FALSE), not null +# locked_at :datetime +# password_digest :string +# provider :string +# uid :string +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_users_on_blocked (blocked) +# index_users_on_email (email) UNIQUE +# index_users_on_locked (locked) +# index_users_on_provider (provider) +# index_users_on_uid (uid) UNIQUE +# class User < ApplicationRecord rolify strict: true @@ -6,7 +32,7 @@ class User < ApplicationRecord attr_accessor :current_password # Callbacks - after_create :create_and_attach_to_default_account + after_create :create_and_attach_to_organization after_create :send_confirmation_email! before_save :downcase_email, if: :will_save_change_to_email? before_save :generate_password_digest @@ -27,7 +53,10 @@ class User < ApplicationRecord has_many :events, dependent: :destroy has_many :questions, dependent: :destroy has_many :votes, dependent: :destroy - has_one :account, through: :members, required: false + + # Managing organization membership (one to many through Member) + has_one :member + has_one :organization, through: :member, required: false # Validations validates :email, presence: true, uniqueness: { case_sensitive: false }, format: { with: URI::MailTo::EMAIL_REGEXP } @@ -79,15 +108,18 @@ def change_email self.confirmed_at = nil end - def create_and_attach_to_default_account + def create_and_attach_to_organization # Creating a default account # TODO: connect users to existing account via SSO or other mechanisms to support invitation - @account = Account.create({name: '__default__'}) + @default_organization = Organization.create!({name: '__default__'}) + + logger.info @default_organization.inspect # Attach user to the default account @member = Member.new() - @member.user = self - @member.account = @account + @member.user_id = self.id + @member.organization_id = @default_organization.id + @member.owner = true @member.save end diff --git a/app/models/vote.rb b/app/models/vote.rb index a390e5ab..84ba0084 100644 --- a/app/models/vote.rb +++ b/app/models/vote.rb @@ -1,3 +1,24 @@ +# == Schema Information +# +# Table name: votes +# +# id :bigint not null, primary key +# choice :integer default("cancel") +# votable_type :string not null +# created_at :datetime not null +# updated_at :datetime not null +# user_id :uuid not null +# votable_id :uuid not null +# +# Indexes +# +# index_votes_on_user_id (user_id) +# index_votes_on_votable (votable_type,votable_id) +# +# Foreign Keys +# +# fk_rails_... (user_id => users.id) +# class Vote < ApplicationRecord belongs_to :user belongs_to :votable, polymorphic: true diff --git a/app/views/cookies/index.html.erb b/app/views/cookies/index.html.erb index e190fa8a..2a83ea1f 100644 --- a/app/views/cookies/index.html.erb +++ b/app/views/cookies/index.html.erb @@ -1,7 +1,7 @@ <%= turbo_frame_tag :cookies_modal do %> <% if session[:cookies_accepted].nil? %>
-
+

🍪 Cooky policy

@@ -11,10 +11,10 @@

<%= link_to "Allow all", cookies_path(cookies_accepted: true), - class: "text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800" %> + class: "text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800 w-36 text-center border border-blue-700 hover:border-blue-800" %> <%= link_to "Bare minimum", cookies_path(cookies_accepted: false), - class: "text-blue-700 hover:text-white border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center mr-2 mb-2 dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:hover:bg-blue-600 dark:focus:ring-blue-800" %> + class: "text-blue-700 hover:text-white border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center mr-2 mb-2 dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:hover:bg-blue-600 dark:focus:ring-blue-800 w-36 text-center" %>
diff --git a/app/views/dashboards/show.html.erb b/app/views/dashboards/show.html.erb index 1d1a5dde..eca1a5da 100644 --- a/app/views/dashboards/show.html.erb +++ b/app/views/dashboards/show.html.erb @@ -1,8 +1,154 @@ -
-
-
- -

Coming Soon

+
+

+ My Insights +

+
+
+ <%= render(DashboardCounterComponent.new(title: "Total", total:@total, icon:'', icon_color:'text-white', link:your_questions_path, positive:20, neutral:50, negative:30, background_color:'bg-gray-800')) %> + <%= render(DashboardCounterComponent.new(title: "Approved", total:@status['approved'], icon:'', icon_color: 'text-blue-400', link:nil, positive:2, neutral:7, negative:1, background_color:'bg-gray-700')) %> + <%= render(DashboardCounterComponent.new(title: "Answered", total:@status['answered']+@status['beinganswered'], icon:'', icon_color:'text-green-400', link:nil, positive:10, neutral:3, negative:7, background_color:'bg-gray-700')) %> + <%= render(DashboardCounterComponent.new(title: "Pending", total:@status['asked'], icon:'', icon_color:'text-yellow-400', link:nil, positive:6, neutral:20, negative:4, background_color:'bg-gray-700')) %> + <%= render(DashboardCounterComponent.new(title: "Rejected", total:@status['rejected'], icon:'', icon_color: 'text-red-400', link:nil, positive:10, neutral:5, negative:25, background_color:'bg-gray-700')) %>
-
+ +
+
+

+ My Key Topics +

+
+ <%= pie_chart Topic.where(user_id: current_user.id).group(:name).limit(5).count, + donut: true, + loading: "Loading...", + empty: "No Data Yet.".html_safe %> +
+
+
+

+ Food for Thoughts +

+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ <%= current_user.profile.nickname %> +
+
+ PEW Points +
+ + + <%= @pew_points %> + +
+
+
+
+
+

+ Trophies (3) +

+ <%= link_to nil do %> + + <% end %> +
+
+
+ <%# Display the user's questions %> +
+ <%= render DashboardQuestionTableComponent.new do |component| %> + <% component.with_header do %> + + + +
+

+ Your Questions +

+

+ <%= link_to your_questions_path do %> + + <% end %> +

+
+ + + Status + + + Upvotes + + + Event + + + Date + + + Tone + + + + + + <% end %> + <%# Looping on the questions %> + <% @questions.each do |question| %> + <% component.with_question do %> + + + <%= question.title.truncate(52) %> + + + <%= display_question_status(question.status) %> + + + <%= question.up_votes %> + + + <%= link_to room_questions_path(question.room) do %> + <%= question.room.event.name.truncate(42) %> + <% end %> + + + <%= question.created_at.strftime('%b %d, %y') %> + + + <%= question.tone %> + + + <%= link_to your_question_path(question.id) do %> + + <% end %> + + + <% end %> + <% end %> + <% end %> +
diff --git a/app/views/events/_form.html.erb b/app/views/events/_form.html.erb index 970d2623..8992e3ca 100644 --- a/app/views/events/_form.html.erb +++ b/app/views/events/_form.html.erb @@ -26,9 +26,16 @@
<%= form.submit class: "text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800" %> <%# unless form.object.new_record? %> - <%= link_to "Cancel", events_path, + <% if controller.action_name == "edit" %> + <%= link_to "Cancel", events_path, class: "w-full text-blue-700 hover:text-white border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:hover:bg-blue-600 dark:focus:ring-blue-800", data: {turbo_frame: :events} %> + <% else %> + <%= button_tag "Cancel", + data: { action: "turbo-modal#hideModal" }, + type: "button", + class: "w-full text-blue-700 hover:text-white border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:hover:bg-blue-600 dark:focus:ring-blue-800" %> + <% end %> <%# end %>
<% end %> diff --git a/app/views/events/new.html.erb b/app/views/events/new.html.erb index 390c1608..5270dced 100644 --- a/app/views/events/new.html.erb +++ b/app/views/events/new.html.erb @@ -1,5 +1,5 @@ <%= turbo_frame_tag "modal" do %> -
+

Create a new event:

<%= render "form", event: @event %>
diff --git a/app/views/legal/index.html.erb b/app/views/legal/index.html.erb index 16550dd4..aeb21b00 100644 --- a/app/views/legal/index.html.erb +++ b/app/views/legal/index.html.erb @@ -7,6 +7,9 @@ Policies
    +
  • + <%= link_to "Terms of Service", "/legal/tos", class: "font-medium text-blue-600 underline dark:text-blue-500 hover:no-underline" %> +
  • <%= link_to "Privacy Policy", "/legal/privacy", class: "font-medium text-blue-600 underline dark:text-blue-500 hover:no-underline" %>
  • diff --git a/app/views/organization/edit.html.erb b/app/views/organization/edit.html.erb new file mode 100644 index 00000000..4a969365 --- /dev/null +++ b/app/views/organization/edit.html.erb @@ -0,0 +1,22 @@ + +<%= turbo_frame_tag "settings_main" do %> + <%= form_with model: @organization do |form| %> + <%= render partial: "shared/form_errors", locals: { object: form.object } %> +
    + <%= form.label :name, "Name of your organization", class: "block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" %> + <%= form.text_field :name, + class:"bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500", + placeholder: "ACME Inc.", + value: @organization.name == "__default__" ? nil : @organization.name %> +
    +
    + <%= form.label :website, "Your organization's website", class: "block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" %> + <%= form.text_field :website, class:"bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500", placeholder: "https://www.acme.com" %> +
    +
    + <%= form.label :description, "A few words about your organization (optional)", class: "block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" %> + <%= form.rich_text_area :description, class: "block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 overflow-auto max-h-32" %> +
    + <%= form.submit class: "text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800" %> + <% end %> +<% end %> diff --git a/app/views/organization/sso.html.erb b/app/views/organization/sso.html.erb new file mode 100644 index 00000000..9a223182 --- /dev/null +++ b/app/views/organization/sso.html.erb @@ -0,0 +1,44 @@ + +<%= turbo_frame_tag "settings_main" do %> + <% if !@organization.domain_verified %> + <%= form_with model: @organization do |form| %> + <%= render partial: "shared/form_errors", locals: { object: form.object } %> +
    + <%= form.label :domain, "Domain of your organization", class: "block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" %> + <%= form.text_field :domain, class:"bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500", placeholder: "example: your_domain.com" %> +
    + <%= form.submit class: "text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800" %> + <% end %> + <% end %> +
    +

    + Domain Ownership Status +

    + <% if @organization.domain_verified %> +
    + + Verified +
    + <% else %> +
    + + Not yet verified +
    + <% end %> +
    + <% if !@organization.domain_verified && @organization.domain.present? %> +
    +
    + Why verifying domain ownership is important? +
    +
    + By verifying ownership, we can ensure that the domain is being used by its rightful owner and not by someone with malicious intent. This helps prevent unauthorized access, phishing scams, and other security threats. Additionally, verifying ownership also helps prevent any potential issues with branding or trademark infringement. By requiring proof of ownership, we can protect both our customers and our service, ensuring a positive and secure experience for all users. +
    +
    + How to verify ownership your domain? +
    +
    +
    +
    + <% end %> +<% end %> diff --git a/app/views/organization/users.html.erb b/app/views/organization/users.html.erb new file mode 100644 index 00000000..276094a2 --- /dev/null +++ b/app/views/organization/users.html.erb @@ -0,0 +1,204 @@ + +<%= turbo_frame_tag "settings_main" do %> +

    + Show Users +

    +
    +
    +
    +
    + + + +
    + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    +
    + Name + + Position + + Status + + Action +
    +
    + + +
    +
    + Jese image +
    +
    Neil Sims
    +
    neil.sims@flowbite.com
    +
    +
    + React Developer + +
    +
    + Online +
    +
    + Edit user +
    +
    + + +
    +
    + Jese image +
    +
    Bonnie Green
    +
    bonnie@flowbite.com
    +
    +
    + Designer + +
    +
    + Online +
    +
    + Edit user +
    +
    + + +
    +
    + Jese image +
    +
    Jese Leos
    +
    jese@flowbite.com
    +
    +
    + Vue JS Developer + +
    +
    + Online +
    +
    + Edit user +
    +
    + + +
    +
    + Jese image +
    +
    Thomas Lean
    +
    thomes@flowbite.com
    +
    +
    + UI/UX Engineer + +
    +
    + Online +
    +
    + Edit user +
    +
    + + +
    +
    + Jese image +
    +
    Leslie Livingston
    +
    leslie@flowbite.com
    +
    +
    + SEO Specialist + +
    +
    + Offline +
    +
    + Edit user +
    +
    +
    +<% end %> diff --git a/app/views/settings/_breadcrumb.html.erb b/app/views/settings/_breadcrumb.html.erb new file mode 100644 index 00000000..45bef5e5 --- /dev/null +++ b/app/views/settings/_breadcrumb.html.erb @@ -0,0 +1,18 @@ + diff --git a/app/views/settings/_sidebar.html.erb b/app/views/settings/_sidebar.html.erb new file mode 100644 index 00000000..11090481 --- /dev/null +++ b/app/views/settings/_sidebar.html.erb @@ -0,0 +1,63 @@ + diff --git a/app/views/settings/index.html.erb b/app/views/settings/index.html.erb index 303a9c83..d0ac57bd 100644 --- a/app/views/settings/index.html.erb +++ b/app/views/settings/index.html.erb @@ -1,71 +1,46 @@
    - + <%= render partial: "sidebar", locals: { organization_id: @organization_id } %> +
    + <%= render "breadcrumb" %> + <%= turbo_frame_tag "settings_main", src: edit_organization_path(@organization_id) do %> +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + Loading... +
    + <% end %> +
    diff --git a/app/views/shared/_top_navigation.html.erb b/app/views/shared/_top_navigation.html.erb index 089520e9..c4232a19 100644 --- a/app/views/shared/_top_navigation.html.erb +++ b/app/views/shared/_top_navigation.html.erb @@ -42,8 +42,8 @@ <% end %>
  • - <%= link_to_unless_current 'Your questions', your_questions_path, class: 'block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 md:dark:hover:text-white dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700' do %> - <%= link_to 'Your questions', your_questions_path, class: 'block py-2 pr-4 pl-3 text-white bg-blue-700 rounded md:bg-transparent md:text-blue-700 md:p-0 dark:text-white', 'aria-current': true %> + <%= link_to_unless_current 'My Insights', dashboard_path, class: 'block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 md:dark:hover:text-white dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700' do %> + <%= link_to 'My Insights', dashboard_path, class: 'block py-2 pr-4 pl-3 text-white bg-blue-700 rounded md:bg-transparent md:text-blue-700 md:p-0 dark:text-white', 'aria-current': true %> <% end %>
  • diff --git a/app/views/shared/_user_menu.html.erb b/app/views/shared/_user_menu.html.erb index 9d3a154c..f39dc967 100644 --- a/app/views/shared/_user_menu.html.erb +++ b/app/views/shared/_user_menu.html.erb @@ -6,15 +6,12 @@ <%= AvatarPresenter.call(current_user, 8) unless current_user.profile.avatar.attached? %> -