diff --git a/app/assets/stylesheets/articles.scss b/app/assets/stylesheets/articles.scss index c05c2384d..b267185a4 100644 --- a/app/assets/stylesheets/articles.scss +++ b/app/assets/stylesheets/articles.scss @@ -1121,6 +1121,23 @@ } } } + .sidebar-sponsorship-level { + text-align: center; + font-weight: bold; + width: calc(100% - 6px); + padding: 26px 0px; + border-radius: 3px; + margin-bottom: 10px; + font-family: $helvetica-condensed; + font-stretch: condensed; + font-size: 1.1em; + @include themeable( + border, + theme-container-border, + 1px solid $outline-color + ); + @include themeable(box-shadow, theme-container-box-shadow, $bold-shadow); + } .widget { width: calc(100% - 30px); padding: 10px 12px; diff --git a/app/assets/stylesheets/partnerships.scss b/app/assets/stylesheets/partnerships.scss new file mode 100644 index 000000000..39d1e905e --- /dev/null +++ b/app/assets/stylesheets/partnerships.scss @@ -0,0 +1,192 @@ +@import 'variables'; +@import 'mixins'; + +.partnerships-container { + width: 800px; + max-width: 96%; + margin: 100px auto; + &.partnerships-container-show { + h1 { + text-align: left; + } + } + h1 { + text-align: center; + font-size: 2.5em; + padding: 1em 0; + img { + height: 2.2em; + width: 2.2em; + transform: rotate(6deg); + border-radius: 0.2em; + vertical-align: -0.7em; + margin-left: 0.3em; + } + } + .partner-credits-explainer { + @include themeable( + background, + theme-container-background, + $white + ); + box-sizing: border-box; + padding: 25px 20px 30px; + border-radius: 12px; + margin: 50px auto 40px; + width: calc(100% - 20px); + @include themeable( + border, + theme-border, + 1px solid $light-medium-gray + ); + @include themeable( + box-shadow, + theme-container-box-shadow, + 2px 2px 8px darken($light-medium-gray, 5%) + ); + .partner-explainer-notice { + padding: 15px; + margin: 25px 0px 12px; + border-radius: 3px; + font-weight: bold; + @include themeable( + background, + theme-container-accent-background, + $light-gray + ); + } + h4 { + font-size: 0.8em; + margin: 3px 0px; + @include themeable( + color, + theme-secondary-color, + $medium-gray + ); + } + h3 { + margin: 5px 0px; + font-size: 1.8em; + @include themeable( + color, + theme-color, + #131a38 + ); + img { + height: 1.2em; + width: 1.2em; + vertical-align: -0.25em; + border-radius: 100px; + margin-right: 0.3em; + } + } + select { + display: block; + font-size: 1.3em; + } + textarea { + width: 80%; + font-size: 1em; + padding: 0.5em; + border-radius: 3px; + display: block; + height: 300px; + @include themeable( + border, + theme-container-border, + 1px solid $light-medium-gray + ); + } + input { + width: 50%; + font-size: 1.3em; + padding: 0.5em; + border-radius: 3px; + display: block; + @include themeable( + border, + theme-container-border, + 1px solid $light-medium-gray + ); + } + a, button { + padding: 5px 20px; + font-size: 1.5em; + border-radius: 8px; + font-weight: bold; + margin: 8px 0px; + display: inline-block; + border: 0px; + @include themeable( + background, + theme-anchor-color, + $bold-blue + ); + @include themeable( + color, + theme-container-background, + white + ); + } + } + .partnership-options { + margin: 40px 0px; + } + .partnership-option-card { + @include themeable( + background, + theme-container-background, + $white + ); + text-align: left; + width: 100%; + @media screen and ( min-width: 820px ){ + width: calc(50% - 15px); + margin-right: 8px; + ul { + height: 220px; + } + } + box-sizing: border-box; + padding: 15px; + border-radius: 12px; + margin: 10px 0px; + display: inline-block; + @include themeable( + border, + theme-border, + 1px solid $light-medium-gray + ); + @include themeable( + box-shadow, + theme-container-box-shadow, + 2px 2px 8px darken($light-medium-gray, 5%) + ); + li { + padding: 8px 0px; + } + h2 { + margin: 0px; + padding: 10px 0px; + border-bottom: 2px solid rgba(0,0,0,0.1); + } + .partnership-option-cta { + display: block; + font-size: 1.6em; + padding: 20px 0px; + text-align: center; + border-radius: 5px; + font-weight: bold; + @include themeable( + background, + theme-anchor-color, + $bold-blue + ); + @include themeable( + color, + theme-container-background, + white + ); + } + } +} \ No newline at end of file diff --git a/app/controllers/partnerships_controller.rb b/app/controllers/partnerships_controller.rb new file mode 100644 index 000000000..e1ba95987 --- /dev/null +++ b/app/controllers/partnerships_controller.rb @@ -0,0 +1,104 @@ +class PartnershipsController < ApplicationController + before_action :set_cache_control_headers, only: %i[index] + before_action :authenticate_user!, only: %i[create] + after_action :verify_authorized + + def index + skip_authorization + set_surrogate_key_header "partnership-index" + end + + def show + skip_authorization + end + + def create + @level = params[:sponsorship_level] + @number_of_credits_needed = credits_for_level + @organization = Organization.find(params[:organization_id]) + update_sponsorship_instructions + authorize @organization, :admin_of_org? + @available_org_credits = @organization.credits.where(spent: false) + if @level == "tag" + @tag = Tag.find_by(name: params[:tag_name]) + @tag.sponsor_organization_id = @organization.id + slackbot_ping("@#{current_user.username} bought a ##{@tag.name} sponsorship for @#{@organization.username}") + if @available_org_credits.size >= @number_of_credits_needed + @tag.save! + spend_credits + redirect_back(fallback_location: "/partnerships") + else + raise "Not enough credits" + end + elsif @level == "media" + # For now. Just ping slack. + slackbot_ping("📹 @#{current_user.username} bought #{@number_of_credits_needed} media credits for @#{@organization.username}") + if @available_org_credits.size >= @number_of_credits_needed + spend_credits + redirect_back(fallback_location: "/partnerships") + end + elsif @level == "editorial" + SlackBot.ping( + message: "@#{current_user.username} bought #{@number_of_credits_needed} credits for @#{@organization.username}", + channel: "incoming-partners", + username: "media_sponsor", + icon_emoji: ":partyparrot:", + ) + slackbot_ping("✍ @#{current_user.username} bought an editorial partnership for @#{@organization.username}") + if @available_org_credits.size >= @number_of_credits_needed + spend_credits + redirect_back(fallback_location: "/partnerships") + end + else + @organization.sponsorship_level = @level + @organization.sponsorship_status = "pending" + @organization.sponsorship_expires_at = (@organization.sponsorship_expires_at || Time.current) + 1.month + slackbot_ping("@#{current_user.username} bought a #{@level} sponsorship for @#{@organization.username}") + if @available_org_credits.size >= @number_of_credits_needed + @organization.save! + spend_credits + redirect_back(fallback_location: "/partnerships") + else + raise "Not enough credits" + end + end + end + + private + + def credits_for_level + if @level == "gold" + 6000 + elsif @level == "silver" + 300 + elsif @level == "tag" + 500 + elsif @level == "bronze" + 50 + elsif @level == "editorial" + 500 + elsif @level == "media" + params[:sponsorship_amount].to_i + else + raise "Invalid level" + end + end + + def update_sponsorship_instructions + @organization.sponsorship_instructions = @organization.sponsorship_instructions + "\n---\n#{Time.current}\n---\n" + params[:sponsorship_instructions].to_s + @organization.sponsorship_instructions_updated_at = Time.current + end + + def spend_credits + @available_org_credits.limit(@number_of_credits_needed).update_all(spent: true) + end + + def slackbot_ping(text) + SlackBot.ping( + message: text, + channel: "incoming-partners", + username: "media_sponsor", + icon_emoji: ":partyparrot:", + ) + end +end diff --git a/app/dashboards/organization_dashboard.rb b/app/dashboards/organization_dashboard.rb index bd5616498..592abe0e0 100644 --- a/app/dashboards/organization_dashboard.rb +++ b/app/dashboards/organization_dashboard.rb @@ -37,7 +37,9 @@ class OrganizationDashboard < Administrate::BaseDashboard cta_body_markdown: Field::Text, sponsorship_url: Field::Text, sponsorship_tagline: Field::Text, - is_gold_sponsor: Field::Boolean, + sponsorship_status: Field::Text, + sponsorship_level: Field::Text, + sponsorship_instructions: Field::Text, sponsorship_blurb_html: Field::Text, sponsorship_featured_number: Field::Number }.freeze @@ -89,7 +91,8 @@ class OrganizationDashboard < Administrate::BaseDashboard sponsorship_url sponsorship_tagline sponsorship_blurb_html - is_gold_sponsor + sponsorship_level + sponsorship_status sponsorship_featured_number ].freeze diff --git a/app/dashboards/tag_dashboard.rb b/app/dashboards/tag_dashboard.rb index c51054247..49c874d01 100644 --- a/app/dashboards/tag_dashboard.rb +++ b/app/dashboards/tag_dashboard.rb @@ -22,6 +22,8 @@ class TagDashboard < Administrate::BaseDashboard pretty_name: Field::String, profile_image: CarrierwaveField, social_image: CarrierwaveField, + sponsorship_organization_id: Field::Number, + sponsorship_status: Field::String, bg_color_hex: Field::String, text_color_hex: Field::String, keywords_for_search: Field::String, @@ -81,6 +83,8 @@ class TagDashboard < Administrate::BaseDashboard social_image bg_color_hex text_color_hex + sponsorship_organization_id + sponsorship_status keywords_for_search buffer_profile_id_code ].freeze diff --git a/app/decorators/organization_decorator.rb b/app/decorators/organization_decorator.rb index 348f99730..bf7053b3e 100644 --- a/app/decorators/organization_decorator.rb +++ b/app/decorators/organization_decorator.rb @@ -25,4 +25,11 @@ class OrganizationDecorator < ApplicationDecorator text: "#ffffff" } end + + def sponsorship_color_hex + hexes = { "gold" => "linear-gradient(to right, #faf0e6 8%, #faf3e6 18%, #fcf6eb 33%);", + "silver" => "linear-gradient(to right, #e3e3e3 8%, #f0eded 18%, #e8e8e8 33%);", + "bronze" => "linear-gradient(to right, #ebe2d3 8%, #f5eee1 18%, #ede6d8 33%);" } + hexes[sponsorship_level] + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index ace98fe5a..5fbcd7e5a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -36,6 +36,7 @@ module ApplicationHelper html_variants classified_listings credits + partnerships ].include?(controller_name) end @@ -193,4 +194,20 @@ module ApplicationHelper def community_qualified_name "The #{ApplicationConfig['COMMUNITY_NAME']} Community" end + + def sponsorship_credits_price(level) + if level == "bronze" + 50 + elsif level == "silver" + 300 + elsif level == "gold" + 4000 + elsif level == "tag" + 500 + elsif level == "media" + 25 + elsif level == "editorial" + 500 + end + end end diff --git a/app/models/organization.rb b/app/models/organization.rb index ad6a17842..84243a7a8 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -26,6 +26,7 @@ class Organization < ApplicationRecord validates :jobs_email, email: true, allow_blank: true validates :text_color_hex, format: /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/, allow_blank: true validates :bg_color_hex, format: /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/, allow_blank: true + validates :sponsorship_level, inclusion: { in: %w[gold silver bronze] }, allow_nil: true validates :slug, presence: true, uniqueness: { case_sensitive: false }, @@ -40,6 +41,7 @@ class Organization < ApplicationRecord message: "Integer only. No sign allowed.", allow_blank: true } validates :tech_stack, :story, length: { maximum: 640 } + validates :sponsorship_status, inclusion: { in: %w[none pending live] } validates :cta_button_url, url: { allow_blank: true, no_local: true, schemes: %w[https http] } validates :cta_button_text, length: { maximum: 20 } diff --git a/app/models/tag.rb b/app/models/tag.rb index 03606ab5c..2b419d1d3 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -18,6 +18,7 @@ class Tag < ActsAsTaggableOn::Tag attr_accessor :tag_moderator_id, :remove_moderator_id belongs_to :badge, optional: true + belongs_to :sponsor_organization, class_name: "Organization", optional: true mount_uploader :profile_image, ProfileImageUploader mount_uploader :social_image, ProfileImageUploader @@ -27,6 +28,7 @@ class Tag < ActsAsTaggableOn::Tag validates :bg_color_hex, format: /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/, allow_nil: true validates :category, inclusion: { in: ALLOWED_CATEGORIES } + validates :sponsorship_status, inclusion: { in: %w[none pending live] } validate :validate_alias before_validation :evaluate_markdown diff --git a/app/policies/organization_policy.rb b/app/policies/organization_policy.rb index 3034230b4..df2a6a37c 100644 --- a/app/policies/organization_policy.rb +++ b/app/policies/organization_policy.rb @@ -17,6 +17,12 @@ class OrganizationPolicy < ApplicationPolicy OrganizationMembership.exists?(user_id: user.id, organization_id: record.id) end + def admin_of_org? + return false if record.blank? + + OrganizationMembership.exists?(user_id: user.id, organization_id: record.id, type_of_user: "admin") + end + def generate_new_secret? update? end diff --git a/app/views/articles/_sidebar.html.erb b/app/views/articles/_sidebar.html.erb index b0cfed846..26b164d0d 100644 --- a/app/views/articles/_sidebar.html.erb +++ b/app/views/articles/_sidebar.html.erb @@ -10,22 +10,11 @@ " alt="emoji heart" /> - <% @sponsors = Organization.where(is_gold_sponsor: true).order("sponsorship_featured_number ASC") %> + <% @sponsors = Organization.where(sponsorship_level: "gold", sponsorship_status: "live").order("sponsorship_featured_number ASC") %> <% if @sponsors.any? %>
<% @sponsors.each do |organization| %> - + <%= render "articles/single_sponsor", organization: organization %> <% end %>
We are grateful for wonderful sponsors who help sustain the <%= ApplicationConfig["COMMUNITY_NAME"] %> community. diff --git a/app/views/articles/_single_sponsor.html.erb b/app/views/articles/_single_sponsor.html.erb new file mode 100644 index 000000000..ed1c75191 --- /dev/null +++ b/app/views/articles/_single_sponsor.html.erb @@ -0,0 +1,12 @@ + diff --git a/app/views/articles/tags/_sidebar.html.erb b/app/views/articles/tags/_sidebar.html.erb index 59525814d..8ec797889 100644 --- a/app/views/articles/tags/_sidebar.html.erb +++ b/app/views/articles/tags/_sidebar.html.erb @@ -35,6 +35,16 @@
<% end %> + <% if @tag_model.sponsor_organization_id && @tag_model.sponsorship_status == "live" %> +
+
+

<%= @tag %> is sponsored by

+
+
+ <%= render "articles/single_sponsor", organization: @tag_model.sponsor_organization %> +
+
+ <% end %> <% if @moderators.present? %>
diff --git a/app/views/credits/index.html.erb b/app/views/credits/index.html.erb index 188e9c763..a61af860f 100644 --- a/app/views/credits/index.html.erb +++ b/app/views/credits/index.html.erb @@ -22,7 +22,14 @@ <% end %> <% if @credits.size > 0 %>
- Create a Listing +

+ Create a Listing +

+ <% if @organizations.present? %> +

+ View Partnership Opportunities +

+ <% end %>
<% end %>
diff --git a/app/views/organizations/_sidebar_additional.html.erb b/app/views/organizations/_sidebar_additional.html.erb index 17852942f..ac25357fa 100644 --- a/app/views/organizations/_sidebar_additional.html.erb +++ b/app/views/organizations/_sidebar_additional.html.erb @@ -1,6 +1,11 @@