diff --git a/.env.sample b/.env.sample
index 54b510d..8c7329e 100644
--- a/.env.sample
+++ b/.env.sample
@@ -8,7 +8,7 @@ NGROK_SUBDOMAIN=
REDIS_DB=0
REDIS_PORT=6379
REDIS_TTL=18000
-REDIS_URL=redis://localhost
+REDIS_URL=redis://localhost:6379/1
SENTRY_DSN=
SIDEKIQ_NAMESPACE=sidekiq_dev
SMTP_DEFAULT_FROM=
diff --git a/Gemfile b/Gemfile
index 4708f22..51b8251 100644
--- a/Gemfile
+++ b/Gemfile
@@ -20,6 +20,8 @@ gem "rails-observers", "~> 0.1.5" # removal candidate
gem "redis", "~> 4.1"
gem "redis-namespace", "~> 1.7" # removal candidate
gem "redis-rails", "~> 5.0" # removal candidate
+gem "rubycritic", "~> 4.5", require: false
+gem "rubycritic-small-badge", "~> 0.2.1", require: false
gem "sass-rails", "~> 6.0" # removal candidate
gem "sentry-raven", "~> 3.0"
gem "sidekiq", "~> 6.0"
@@ -27,7 +29,7 @@ gem "skylight", "~> 4.3"
gem "stimulus_reflex", "~> 3.2"
gem "turbolinks", "~> 5.2"
gem "view_component", "~> 2.7"
-gem "webpacker", "~> 4.0"
+gem "webpacker", "~> 5.1"
group :development, :test do
gem "bullet", "~> 6.1"
diff --git a/Gemfile.lock b/Gemfile.lock
index f91c474..ab0755f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -70,6 +70,10 @@ GEM
activerecord (>= 3.2, < 7.0)
rake (>= 10.4, < 14.0)
ast (2.4.0)
+ axiom-types (0.1.1)
+ descendants_tracker (~> 0.0.4)
+ ice_nine (~> 0.11.0)
+ thread_safe (~> 0.3, >= 0.3.1)
backport (1.1.2)
bcrypt (3.1.13)
benchmark (0.1.0)
@@ -93,23 +97,29 @@ GEM
code_analyzer (0.5.1)
sexp_processor
coderay (1.1.2)
+ coercible (1.0.0)
+ descendants_tracker (~> 0.0.1)
colorize (0.8.1)
concurrent-ruby (1.1.6)
connection_pool (2.2.2)
crass (1.0.6)
database_consistency (0.8.0)
activerecord (>= 3.2)
+ descendants_tracker (0.0.4)
+ thread_safe (~> 0.3, >= 0.3.1)
devise (4.7.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
+ docile (1.3.2)
dotenv (2.7.5)
dotenv-rails (2.7.5)
dotenv (= 2.7.5)
railties (>= 3.2, < 6.1)
e2mmap (0.1.0)
+ equalizer (0.0.11)
equatable (0.6.1)
erb_lint (0.0.33)
activesupport
@@ -130,6 +140,15 @@ GEM
ruby_parser (>= 3.14.1)
ffaker (2.15.0)
ffi (1.12.2)
+ flay (2.12.1)
+ erubis (~> 2.7.0)
+ path_expander (~> 1.0)
+ ruby_parser (~> 3.0)
+ sexp_processor (~> 4.0)
+ flog (4.6.4)
+ path_expander (~> 1.0)
+ ruby_parser (~> 3.1, > 3.1.0)
+ sexp_processor (~> 4.8)
friendly_id (5.3.0)
activerecord (>= 4.0.0)
globalid (0.4.2)
@@ -138,11 +157,13 @@ GEM
html_tokenizer (0.0.7)
i18n (1.8.2)
concurrent-ruby (~> 1.0)
+ ice_nine (0.11.2)
jaro_winkler (1.5.4)
jbuilder (2.10.0)
activesupport (>= 5.0.0)
json (2.3.0)
jwt (2.2.1)
+ kwalify (0.7.2)
launchy (2.5.0)
addressable (~> 2.7)
letter_opener (1.7.0)
@@ -203,12 +224,14 @@ GEM
pastel (0.7.3)
equatable (~> 0.6)
tty-color (~> 0.5)
+ path_expander (1.1.0)
pg (1.2.3)
pry (0.13.1)
coderay (~> 1.1)
method_source (~> 1.0)
pry-rails (0.3.9)
pry (>= 0.10.4)
+ psych (3.1.0)
public_suffix (4.0.5)
puma (4.3.5)
nio4r (~> 2.0)
@@ -279,6 +302,13 @@ GEM
redis-store (>= 1.2, < 2)
redis-store (1.8.2)
redis (>= 4, < 5)
+ reek (6.0.1)
+ kwalify (~> 0.7.0)
+ parser (>= 2.5.0.0, < 2.8, != 2.5.1.1)
+ psych (~> 3.1.0)
+ rainbow (>= 2.0, < 4.0)
+ repo-small-badge (0.2.7)
+ victor (~> 0.2.8)
require_all (3.0.0)
responders (3.0.0)
actionpack (>= 5.0)
@@ -301,6 +331,19 @@ GEM
ruby_dep (1.5.0)
ruby_parser (3.14.2)
sexp_processor (~> 4.9)
+ rubycritic (4.5.0)
+ flay (~> 2.8)
+ flog (~> 4.4)
+ launchy (>= 2.0.0)
+ parser (>= 2.6.0)
+ rainbow (~> 3.0)
+ reek (~> 6.0, < 7.0)
+ ruby_parser (~> 3.8)
+ simplecov (>= 0.17.0)
+ tty-which (~> 0.4.0)
+ virtus (~> 1.0)
+ rubycritic-small-badge (0.2.1)
+ repo-small-badge (~> 0.2.7)
sass-rails (6.0.0)
sassc-rails (~> 2.1, >= 2.1.1)
sassc (2.3.0)
@@ -311,6 +354,7 @@ GEM
sprockets (> 3.0)
sprockets-rails
tilt
+ semantic_range (2.3.0)
sentry-raven (3.0.0)
faraday (>= 1.0)
sexp_processor (4.14.1)
@@ -319,6 +363,10 @@ GEM
rack (~> 2.0)
rack-protection (>= 2.0.0)
redis (>= 4.1.0)
+ simplecov (0.18.5)
+ docile (~> 1.1)
+ simplecov-html (~> 0.11)
+ simplecov-html (0.12.2)
skylight (4.3.0)
skylight-core (= 4.3.0)
skylight-core (4.3.0)
@@ -374,6 +422,7 @@ GEM
tty-cursor (~> 0.7)
tty-color (0.5.1)
tty-cursor (0.7.1)
+ tty-which (0.4.2)
turbolinks (5.2.1)
turbolinks-source (~> 5.2)
turbolinks-source (5.2.0)
@@ -382,14 +431,21 @@ GEM
unicode-display_width (1.7.0)
unicode_utils (1.4.0)
uniform_notifier (1.13.0)
+ victor (0.2.8)
view_component (2.7.0)
activesupport (>= 5.0.0, < 7.0)
+ virtus (1.0.5)
+ axiom-types (~> 0.1)
+ coercible (~> 1.0)
+ descendants_tracker (~> 0.0, >= 0.0.3)
+ equalizer (~> 0.0, >= 0.0.9)
warden (1.2.8)
rack (>= 2.0.6)
- webpacker (4.2.2)
- activesupport (>= 4.2)
+ webpacker (5.1.1)
+ activesupport (>= 5.2)
rack-proxy (>= 0.6.1)
- railties (>= 4.2)
+ railties (>= 5.2)
+ semantic_range (>= 2.3.0)
websocket-driver (0.7.2)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.4)
@@ -432,6 +488,8 @@ DEPENDENCIES
redis (~> 4.1)
redis-namespace (~> 1.7)
redis-rails (~> 5.0)
+ rubycritic (~> 4.5)
+ rubycritic-small-badge (~> 0.2.1)
sass-rails (~> 6.0)
sentry-raven (~> 3.0)
sidekiq (~> 6.0)
@@ -445,7 +503,7 @@ DEPENDENCIES
turbolinks (~> 5.2)
tzinfo-data
view_component (~> 2.7)
- webpacker (~> 4.0)
+ webpacker (~> 5.1)
RUBY VERSION
ruby 2.6.6p146
diff --git a/Rakefile b/Rakefile
index 9a5ea73..0255ee0 100644
--- a/Rakefile
+++ b/Rakefile
@@ -2,5 +2,15 @@
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require_relative "config/application"
+require "rubycritic_small_badge"
+require "rubycritic/rake_task"
Rails.application.load_tasks
+
+RubyCriticSmallBadge.configure do |config|
+ config.minimum_score = 90
+end
+RubyCritic::RakeTask.new do |task|
+ task.options = %(--custom-format RubyCriticSmallBadge::Report
+--minimum-score #{RubyCriticSmallBadge.config.minimum_score})
+end
diff --git a/app/components/application_component.rb b/app/components/application_component.rb
new file mode 100644
index 0000000..fdbea0c
--- /dev/null
+++ b/app/components/application_component.rb
@@ -0,0 +1,12 @@
+class ApplicationComponent < ViewComponent::Base
+ private
+
+ def find_or_fallback(supplied, valid, fallback)
+ if valid.include?(supplied)
+ supplied
+ else
+ raise ArgumentError if Rails.env.development?
+ fallback
+ end
+ end
+end
diff --git a/app/components/badge_component.html.erb b/app/components/badge_component.html.erb
new file mode 100644
index 0000000..29adc8b
--- /dev/null
+++ b/app/components/badge_component.html.erb
@@ -0,0 +1,8 @@
+
+
+ <%= tag.small(class: "text-xs text-red-500 tracking-widest font-medium") { org_name } if org_name %>
+
<%= badge.title %>
+ <%= image_tag badge.asset_url, class: "mt-3", alt: badge.title %>
+ <%# <%= tag.p(class: "text-gray-600") { badge.description } if badge.description %>
+
+
diff --git a/app/components/badge_component.rb b/app/components/badge_component.rb
new file mode 100644
index 0000000..ccd6f91
--- /dev/null
+++ b/app/components/badge_component.rb
@@ -0,0 +1,13 @@
+class BadgeComponent < ApplicationComponent
+ def initialize(badge:)
+ @badge = badge
+ end
+
+ private
+
+ attr_reader :badge
+
+ def org_name
+ badge.organization ? badge.organization.name : false
+ end
+end
diff --git a/app/components/example_component.html.erb b/app/components/example_component.html.erb
deleted file mode 100644
index 4917f30..0000000
--- a/app/components/example_component.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-Add Example template here
diff --git a/app/components/example_component.rb b/app/components/example_component.rb
deleted file mode 100644
index 10aaa26..0000000
--- a/app/components/example_component.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-class ExampleComponent < ViewComponent::Base
- def initialize(title:, content:)
- @title = title
- @content = content
- end
-end
diff --git a/app/components/flash_component.html.erb b/app/components/flash_component.html.erb
new file mode 100644
index 0000000..402100b
--- /dev/null
+++ b/app/components/flash_component.html.erb
@@ -0,0 +1,8 @@
+<% flash.each do |msg_type, message| %>
+
+
+
+ <%= message %>
+
+
+<% end %>
diff --git a/app/components/flash_component.rb b/app/components/flash_component.rb
new file mode 100644
index 0000000..da515bb
--- /dev/null
+++ b/app/components/flash_component.rb
@@ -0,0 +1,22 @@
+class FlashComponent < ApplicationComponent
+ def initialize(flash: nil)
+ @flash = flash
+ end
+
+ private
+
+ attr_reader :flash
+
+ def class_for(flash_type)
+ {
+ success: "alert-success",
+ error: "alert-danger",
+ alert: "alert-warning",
+ notice: "alert-info"
+ }.stringify_keys[flash_type.to_s] || flash_type.to_s
+ end
+
+ def render?
+ flash
+ end
+end
diff --git a/app/components/footer_component.html.erb b/app/components/footer_component.html.erb
new file mode 100644
index 0000000..9430855
--- /dev/null
+++ b/app/components/footer_component.html.erb
@@ -0,0 +1,12 @@
+
diff --git a/app/components/footer_component.rb b/app/components/footer_component.rb
new file mode 100644
index 0000000..bfbcd31
--- /dev/null
+++ b/app/components/footer_component.rb
@@ -0,0 +1,2 @@
+class FooterComponent < ApplicationComponent
+end
diff --git a/app/components/form/errors_component.html.erb b/app/components/form/errors_component.html.erb
new file mode 100644
index 0000000..833b9da
--- /dev/null
+++ b/app/components/form/errors_component.html.erb
@@ -0,0 +1,10 @@
+
+
+ <%= pluralize(object.errors.count, "error") %> prohibited saving:
+
+
+ <% object.errors.full_messages.each do |message| %>
+ - <%= message %>
+ <% end %>
+
+
diff --git a/app/components/form/errors_component.rb b/app/components/form/errors_component.rb
new file mode 100644
index 0000000..bda95ee
--- /dev/null
+++ b/app/components/form/errors_component.rb
@@ -0,0 +1,13 @@
+class Form::ErrorsComponent < ApplicationComponent
+ def initialize(object: nil)
+ @object = object
+ end
+
+ private
+
+ attr_reader :object
+
+ def render?
+ object&.errors&.any?
+ end
+end
diff --git a/app/components/header/navbar_component.html.erb b/app/components/header/navbar_component.html.erb
new file mode 100644
index 0000000..e00c25d
--- /dev/null
+++ b/app/components/header/navbar_component.html.erb
@@ -0,0 +1,6 @@
+
diff --git a/app/components/header/navbar_component.rb b/app/components/header/navbar_component.rb
new file mode 100644
index 0000000..a9327d2
--- /dev/null
+++ b/app/components/header/navbar_component.rb
@@ -0,0 +1,4 @@
+module Header
+ class NavbarComponent < ApplicationComponent
+ end
+end
diff --git a/app/components/header_component.html.erb b/app/components/header_component.html.erb
new file mode 100644
index 0000000..ade2e7f
--- /dev/null
+++ b/app/components/header_component.html.erb
@@ -0,0 +1,14 @@
+
diff --git a/app/components/header_component.rb b/app/components/header_component.rb
new file mode 100644
index 0000000..efd59d8
--- /dev/null
+++ b/app/components/header_component.rb
@@ -0,0 +1,2 @@
+class HeaderComponent < ApplicationComponent
+end
diff --git a/app/components/page_component.html.erb b/app/components/page_component.html.erb
new file mode 100644
index 0000000..64fff24
--- /dev/null
+++ b/app/components/page_component.html.erb
@@ -0,0 +1,10 @@
+
+
+ <%= tag.h1(class: "sm:text-3xl text-2xl font-medium text-center title-font text-white mb-4") { title } if title %>
+ <%= tag.p(class: "text-base leading-relaxed xl:w-2/4 lg:w-3/4 mx-auto") { subtitle } if subtitle %>
+
+
+ <%= body %>
+
+ <%= actions %>
+
diff --git a/app/components/page_component.rb b/app/components/page_component.rb
new file mode 100644
index 0000000..9c33ca7
--- /dev/null
+++ b/app/components/page_component.rb
@@ -0,0 +1,11 @@
+class PageComponent < ApplicationComponent
+ with_content_areas :title, :subtitle, :body, :actions
+
+ # def initialize(body_width: "lg:w-1/2 md:w-2/3")
+ # @body_width = body_width
+ # end
+
+ private
+
+ # attr_reader :body_width
+end
diff --git a/app/controllers/badges_controller.rb b/app/controllers/badges_controller.rb
new file mode 100644
index 0000000..d43a749
--- /dev/null
+++ b/app/controllers/badges_controller.rb
@@ -0,0 +1,75 @@
+class BadgesController < ApplicationController
+ before_action :set_badge, only: [:show, :edit, :update, :destroy]
+
+ # GET /badges
+ # GET /badges.json
+ def index
+ @badges = Badge.all
+ end
+
+ # GET /badges/1
+ # GET /badges/1.json
+ def show
+ end
+
+ # GET /badges/new
+ def new
+ @badge = Badge.new
+ end
+
+ # GET /badges/1/edit
+ def edit
+ end
+
+ # POST /badges
+ # POST /badges.json
+ def create
+ @badge = Badge.new(badge_params)
+
+ respond_to do |format|
+ if @badge.save
+ format.html { redirect_to @badge, notice: "Badge was successfully created." }
+ format.json { render :show, status: :created, location: @badge }
+ else
+ format.html { render :new }
+ format.json { render json: @badge.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # PATCH/PUT /badges/1
+ # PATCH/PUT /badges/1.json
+ def update
+ respond_to do |format|
+ if @badge.update(badge_params)
+ format.html { redirect_to @badge, notice: "Badge was successfully updated." }
+ format.json { render :show, status: :ok, location: @badge }
+ else
+ format.html { render :edit }
+ format.json { render json: @badge.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /badges/1
+ # DELETE /badges/1.json
+ def destroy
+ @badge.destroy
+ respond_to do |format|
+ format.html { redirect_to badges_url, notice: "Badge was successfully destroyed." }
+ format.json { head :no_content }
+ end
+ end
+
+ private
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_badge
+ @badge = Badge.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def badge_params
+ params.require(:badge).permit(:title, :asset_url, :source_url, :belongs_to)
+ end
+end
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb
new file mode 100644
index 0000000..124946d
--- /dev/null
+++ b/app/controllers/categories_controller.rb
@@ -0,0 +1,75 @@
+class CategoriesController < ApplicationController
+ before_action :set_category, only: [:show, :edit, :update, :destroy]
+
+ # GET /categories
+ # GET /categories.json
+ def index
+ @categories = Category.all
+ end
+
+ # GET /categories/1
+ # GET /categories/1.json
+ def show
+ end
+
+ # GET /categories/new
+ def new
+ @category = Category.new
+ end
+
+ # GET /categories/1/edit
+ def edit
+ end
+
+ # POST /categories
+ # POST /categories.json
+ def create
+ @category = Category.new(category_params)
+
+ respond_to do |format|
+ if @category.save
+ format.html { redirect_to @category, notice: "Category was successfully created." }
+ format.json { render :show, status: :created, location: @category }
+ else
+ format.html { render :new }
+ format.json { render json: @category.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # PATCH/PUT /categories/1
+ # PATCH/PUT /categories/1.json
+ def update
+ respond_to do |format|
+ if @category.update(category_params)
+ format.html { redirect_to @category, notice: "Category was successfully updated." }
+ format.json { render :show, status: :ok, location: @category }
+ else
+ format.html { render :edit }
+ format.json { render json: @category.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /categories/1
+ # DELETE /categories/1.json
+ def destroy
+ @category.destroy
+ respond_to do |format|
+ format.html { redirect_to categories_url, notice: "Category was successfully destroyed." }
+ format.json { head :no_content }
+ end
+ end
+
+ private
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_category
+ @category = Category.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def category_params
+ params.require(:category).permit(:name, :categorizations_count)
+ end
+end
diff --git a/app/controllers/organizations_controller.rb b/app/controllers/organizations_controller.rb
new file mode 100644
index 0000000..fb7af9b
--- /dev/null
+++ b/app/controllers/organizations_controller.rb
@@ -0,0 +1,75 @@
+class OrganizationsController < ApplicationController
+ before_action :set_organization, only: [:show, :edit, :update, :destroy]
+
+ # GET /organizations
+ # GET /organizations.json
+ def index
+ @organizations = Organization.all
+ end
+
+ # GET /organizations/1
+ # GET /organizations/1.json
+ def show
+ end
+
+ # GET /organizations/new
+ def new
+ @organization = Organization.new
+ end
+
+ # GET /organizations/1/edit
+ def edit
+ end
+
+ # POST /organizations
+ # POST /organizations.json
+ def create
+ @organization = Organization.new(organization_params)
+
+ respond_to do |format|
+ if @organization.save
+ format.html { redirect_to @organization, notice: "Organization was successfully created." }
+ format.json { render :show, status: :created, location: @organization }
+ else
+ format.html { render :new }
+ format.json { render json: @organization.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # PATCH/PUT /organizations/1
+ # PATCH/PUT /organizations/1.json
+ def update
+ respond_to do |format|
+ if @organization.update(organization_params)
+ format.html { redirect_to @organization, notice: "Organization was successfully updated." }
+ format.json { render :show, status: :ok, location: @organization }
+ else
+ format.html { render :edit }
+ format.json { render json: @organization.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /organizations/1
+ # DELETE /organizations/1.json
+ def destroy
+ @organization.destroy
+ respond_to do |format|
+ format.html { redirect_to organizations_url, notice: "Organization was successfully destroyed." }
+ format.json { head :no_content }
+ end
+ end
+
+ private
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_organization
+ @organization = Organization.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def organization_params
+ params.require(:organization).permit(:name, :url, :type)
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 98559e8..de6be79 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,10 +1,2 @@
module ApplicationHelper
- def bootstrap_class_for(flash_type)
- {
- success: "alert-success",
- error: "alert-danger",
- alert: "alert-warning",
- notice: "alert-info"
- }.stringify_keys[flash_type.to_s] || flash_type.to_s
- end
end
diff --git a/app/helpers/badges_helper.rb b/app/helpers/badges_helper.rb
new file mode 100644
index 0000000..a42504e
--- /dev/null
+++ b/app/helpers/badges_helper.rb
@@ -0,0 +1,2 @@
+module BadgesHelper
+end
diff --git a/app/helpers/categories_helper.rb b/app/helpers/categories_helper.rb
new file mode 100644
index 0000000..e06f315
--- /dev/null
+++ b/app/helpers/categories_helper.rb
@@ -0,0 +1,2 @@
+module CategoriesHelper
+end
diff --git a/app/helpers/organizations_helper.rb b/app/helpers/organizations_helper.rb
new file mode 100644
index 0000000..24cc9a8
--- /dev/null
+++ b/app/helpers/organizations_helper.rb
@@ -0,0 +1,2 @@
+module OrganizationsHelper
+end
diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js
index 842d687..1bf9a4e 100644
--- a/app/javascript/controllers/index.js
+++ b/app/javascript/controllers/index.js
@@ -20,3 +20,16 @@ application.load(
)
)
StimulusReflex.initialize(application, { consumer, controller, debug: false })
+
+import {
+ Dropdown,
+ Modal,
+ Tabs,
+ Popover,
+ Toggle
+} from 'tailwindcss-stimulus-components'
+application.register('dropdown', Dropdown)
+application.register('modal', Modal)
+application.register('tabs', Tabs)
+application.register('popover', Popover)
+application.register('toggle', Toggle)
diff --git a/app/javascript/images/ruby.svg b/app/javascript/images/ruby.svg
new file mode 100644
index 0000000..978383c
--- /dev/null
+++ b/app/javascript/images/ruby.svg
@@ -0,0 +1,259 @@
+
+
diff --git a/app/javascript/packs/application.css b/app/javascript/packs/application.css
new file mode 100644
index 0000000..044bc68
--- /dev/null
+++ b/app/javascript/packs/application.css
@@ -0,0 +1,815 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+:root {
+ --base-500: #a0aec0;
+ --base-200: #e2e8f0;
+ --base-300: #e2e8f0;
+ --base-400: #cbd5e0;
+ --main-500: #667eea;
+ --main-400: #7f9cf5;
+ --main-200: #c3dafe;
+ --main-100: #ebf4ff;
+ --solid: #fff;
+ --solid-900: #4a5568;
+ --sidebar-bg: #edf2f7;
+ --sidebar-color: #4a5568;
+ --shadow: 0 10px 15px -3px rgba(36, 69, 101, 0.19),
+ 0 4px 6px -2px rgb(208, 220, 232);
+ --shadow-active: 0 0 0 2px currentColor,
+ 0 10px 15px -3px rgba(36, 69, 101, 0.19), 0 4px 6px -2px rgb(208, 220, 232);
+ --opener: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='white' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 24 24'%3E%3Cpath d='M3 12h18M3 6h18M3 18h18'/%3E%3C/svg%3E");
+ --opener-active: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='white' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round' class='css-i6dzq1' viewBox='0 0 24 24'%3E%3Cpath d='M19 12H5M12 19l-7-7 7-7'/%3E%3C/svg%3E");
+}
+
+.dark-mode {
+ --base-500: #718096;
+ --base-200: #2d3748;
+ --base-300: #4a5568;
+ --base-400: #4a5568;
+ --main-500: #667eea;
+ --main-200: #4a5568;
+ --main-100: #2d3648;
+ --solid: #1a202c;
+ --solid-900: #d1dcec;
+ --sidebar-bg: #2d3648;
+ --sidebar-color: #fff;
+ --shadow: 0 10px 15px -3px rgb(26, 32, 45), 0 4px 6px -2px rgb(48, 58, 84);
+ --shadow-active: 0 0 0 2px currentColor, 0 10px 15px -3px rgba(0, 0, 0, 0.35),
+ 0 4px 6px -2px rgb(76, 82, 90);
+}
+
+.red:not(.dark-mode) {
+ --main-200: #fed7d7;
+ --main-100: #fed7d7;
+}
+
+.red {
+ --main-500: #f56565;
+ --main-400: #fc8181;
+}
+
+.orange {
+ --main-500: #ed8936;
+ --main-400: #f6ad55;
+}
+
+.orange:not(.dark-mode) {
+ --main-200: #feebc8;
+ --main-100: #fffaf0;
+}
+
+.green {
+ --main-500: #48bb78;
+ --main-400: #68d391;
+}
+
+.green:not(.dark-mode) {
+ --main-200: #c6f6d5;
+ --main-100: #c6f6d5;
+}
+
+.teal {
+ --main-500: #38b2ac;
+ --main-400: #4fd1c5;
+}
+
+.teal:not(.dark-mode) {
+ --main-200: #b2f5ea;
+ --main-100: #e6fffa;
+}
+
+.blue {
+ --main-500: #4299e1;
+ --main-400: #63b3ed;
+}
+
+.blue:not(.dark-mode) {
+ --main-200: #bee3f8;
+ --main-100: #ebf8ff;
+}
+
+.purple {
+ --main-500: #9f7aea;
+ --main-400: #b794f4;
+}
+
+.purple:not(.dark-mode) {
+ --main-200: #e9d8fd;
+ --main-100: #faf5ff;
+}
+
+.pink {
+ --main-500: #ed64a6;
+ --main-400: #f687b3;
+}
+
+.pink:not(.dark-mode) {
+ --main-200: #fed7e2;
+ --main-100: #fff5f7;
+}
+
+button {
+ user-select: none;
+}
+
+button,
+button:focus {
+ outline: 0;
+}
+
+body {
+ margin: 0;
+ font-family: 'Raleway', -apple-system, BlinkMacSystemFont, 'Segoe UI',
+ 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
+ 'Helvetica Neue', sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ overflow: hidden;
+}
+
+iframe {
+ width: 100%;
+ height: 100%;
+ border: 0;
+ background-color: var(--solid);
+}
+
+.app {
+ height: 100vh;
+}
+
+.app.dark-mode {
+ background-color: #1a202c;
+}
+
+.main {
+ height: calc(100vh - 148px);
+ overflow: auto;
+ transition: 0.3s;
+}
+::-webkit-scrollbar-thumb,
+::-webkit-scrollbar-track,
+::-webkit-scrollbar {
+ display: none;
+}
+
+.sidebar {
+ height: 100vh;
+ width: 160px;
+ z-index: 1;
+ position: fixed;
+ overflow: auto;
+ left: 0;
+ transform: translateX(-100%);
+ padding: 20px;
+ top: 0;
+ background-color: var(--sidebar-bg);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ transition: transform 0.3s;
+}
+
+.app.has-sidebar .sidebar {
+ box-shadow: 2px 68px 10px rgba(194, 206, 219, 0.68);
+}
+.dark-mode.has-sidebar .sidebar {
+ box-shadow: 2px 68px 10px rgba(26, 32, 44, 0.8);
+}
+
+.block-category {
+ margin-bottom: 10px;
+ color: var(--sidebar-color);
+ font-size: 13px;
+ text-transform: uppercase;
+ font-weight: 600;
+ margin-bottom: 10px;
+}
+
+.block-item {
+ padding: 0;
+ border: 0;
+ border-radius: 4px;
+ box-shadow: var(--shadow);
+ overflow: hidden;
+ color: var(--main-500);
+}
+
+.block-item.is-active {
+ background-color: var(--main-500);
+ box-shadow: var(--shadow-active);
+}
+
+.block-item.is-active > svg {
+ opacity: 0.8;
+}
+
+.block-item + .block-item {
+ margin-top: 20px;
+}
+.blocks + .blocks {
+ margin-top: 30px;
+}
+svg {
+ width: 100%;
+}
+
+.app.has-sidebar .toolbar {
+ padding: 0 16px 0 176px;
+}
+
+.toolbar {
+ background-color: var(--main-500);
+ transition: padding 0.3s;
+ padding: 0 16px;
+ height: 64px;
+ display: flex;
+ align-items: center;
+}
+
+.app.has-sidebar .sidebar {
+ transform: translateX(0);
+}
+
+.switcher {
+ display: flex;
+ padding: 0 10px;
+ height: 32px;
+ border-radius: 20px;
+ background-color: #fff;
+ align-items: center;
+ margin-right: 16px;
+}
+
+.theme-button {
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+}
+
+.theme-button.is-active {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='white' stroke-width='3' fill='none' stroke-linecap='round' stroke-linejoin='round' class='css-i6dzq1' viewBox='0 0 24 24'%3E%3Cpath d='M20 6L9 17l-5-5'/%3E%3C/svg%3E");
+ background-size: 10px;
+ content: '';
+ background-repeat: no-repeat;
+ background-size: 14px;
+ background-position: center;
+}
+
+.theme-button + .theme-button {
+ margin-left: 5px;
+}
+
+.opener {
+ color: #fff;
+ display: flex;
+ align-items: center;
+ font-weight: 600;
+ flex-shrink: 0;
+ position: relative;
+}
+
+.opener::before {
+ background-image: var(--opener);
+ display: inline-block;
+ width: 18px;
+ flex-shrink: 0;
+ height: 18px;
+ content: '';
+ background-size: cover;
+ background-repeat: no-repeat;
+ margin-right: 4px;
+}
+
+.app.has-sidebar .opener::before {
+ background-image: var(--opener-active);
+}
+
+.mode {
+ background-color: rgba(255, 255, 255, 0.192);
+ border-radius: 30px;
+ width: 58px;
+ position: relative;
+ height: 32px;
+ flex-shrink: 0;
+ margin-left: 16px;
+}
+
+.mode::before {
+ width: 32px;
+ height: 100%;
+ content: '';
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='white' stroke-width='2.4' fill='white' stroke-linecap='round' stroke-linejoin='round' class='css-i6dzq1' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='5'/%3E%3Cpath d='M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42'/%3E%3C/svg%3E");
+ background-size: cover;
+ left: 0;
+ position: absolute;
+ top: 0;
+ background-repeat: no-repeat;
+ background-size: 50%;
+ transition: 0.3s;
+ background-position: center;
+}
+
+.mode::after {
+ width: 20px;
+ height: 20px;
+ position: absolute;
+ right: 6px;
+ top: 6px;
+ background-color: var(--main-500);
+ content: '';
+ border-radius: 50%;
+ transition: transform 0.3s;
+}
+
+.app.dark-mode .mode {
+ background-color: rgba(0, 0, 0, 0.171);
+}
+.app.dark-mode .mode::after {
+ transform: translateX(-24px);
+}
+.app.dark-mode .mode::before {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='white' stroke-width='2' fill='white' stroke-linecap='round' stroke-linejoin='round' class='css-i6dzq1' viewBox='0 0 24 24'%3E%3Cpath d='M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z'/%3E%3C/svg%3E");
+ transform: translateX(26px);
+}
+
+.device {
+ color: #fff;
+ opacity: 0.3;
+ transition: 0.3s;
+}
+
+.device.is-active {
+ opacity: 1;
+}
+
+.device + .device {
+ margin-left: 8px;
+}
+
+.view {
+ position: relative;
+}
+
+.app.desktop .view {
+ height: 100%;
+}
+
+.device > svg {
+ width: 24px;
+}
+
+.app.phone .view iframe {
+ height: 736px;
+ width: 414px;
+ margin: 60px auto 0;
+ border: 2px solid var(--base-300);
+}
+
+.app.phone .view {
+ width: 450px;
+ height: 880px;
+ background-color: var(--sidebar-bg);
+ border-radius: 30px;
+ border: 2px solid var(--base-300);
+ margin: 60px auto;
+}
+
+.app.phone:not(.dark-mode) .view::before,
+.app.phone:not(.dark-mode) .view::after {
+ opacity: 0.6;
+}
+
+.app.phone .view::before {
+ position: absolute;
+ left: 50%;
+ bottom: 20px;
+ transform: translateX(-50%);
+ border: 2px solid var(--base-400);
+ content: '';
+ width: 40px;
+ border-radius: 50%;
+ height: 40px;
+}
+
+.app.phone .view::after {
+ content: '';
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+ top: 23px;
+ width: 100px;
+ height: 16px;
+ border-radius: 40px;
+ border: 2px solid var(--base-400);
+}
+
+.app.tablet .view {
+ margin: 60px auto;
+ width: 868px;
+ height: 1164px;
+ background-color: var(--sidebar-bg);
+ border-radius: 30px;
+ border: 2px solid var(--base-300);
+}
+
+.app.tablet .view iframe {
+ height: 1024px;
+ width: 768px;
+ margin: 50px auto 0;
+ border: 2px solid var(--base-300);
+}
+
+.app.tablet .view::before {
+ position: absolute;
+ left: 50%;
+ bottom: 20px;
+ transform: translateX(-50%);
+ border: 2px solid var(--base-400);
+ content: '';
+ width: 50px;
+ border-radius: 50%;
+ height: 50px;
+}
+
+.markup {
+ display: none;
+}
+
+.toolbar > :nth-child(2) {
+ margin-left: auto;
+}
+
+.copy-the-block {
+ border: 1px solid rgba(255, 255, 255, 0.4);
+ padding: 0 12px;
+ flex-shrink: 0;
+ border-radius: 20px;
+ margin-right: 14px;
+ display: flex;
+ font-weight: 600;
+ align-items: center;
+ color: #fff;
+ height: 32px;
+ overflow: hidden;
+ position: relative;
+ justify-content: center;
+ width: 130px;
+ text-align: center;
+ font-size: 13px;
+ transition: 0.3s;
+}
+
+.copy-to-clipboard {
+ white-space: nowrap;
+ width: auto;
+}
+
+.copy-the-block > svg {
+ width: 16px;
+ flex-shrink: 0;
+ margin-right: 6px;
+}
+
+.copy-to-clipboard > svg {
+ width: 13px;
+}
+
+.knyttneve {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ color: #fff;
+ border-radius: 20px;
+ height: 32px;
+ width: 210px;
+ font-size: 13px;
+ text-align: center;
+ overflow: hidden;
+ font-weight: 500;
+}
+
+.knyttneve span {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ text-align: center;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: 0.3s;
+}
+
+.id {
+ transform: translateY(-100%);
+}
+
+.knyttneve:hover .id {
+ transform: none;
+}
+
+.follow svg {
+ width: 14px;
+ margin-right: 8px;
+ height: 14px;
+}
+
+.knyttneve:hover .follow {
+ transform: translateY(100%);
+}
+
+.copy-textarea {
+ position: absolute;
+ right: -9999990px;
+ top: -999999999px;
+ opacity: 0;
+}
+
+.codes {
+ float: right;
+ display: none;
+ width: 100%;
+ transition: 0.3s;
+}
+
+.app.has-sidebar .codes {
+ width: calc(100% - 160px);
+}
+
+.dark-mode .view.show-code,
+.dark-mode .codes pre {
+ background-color: #1a202c !important;
+}
+
+.view.show-code,
+.codes pre {
+ background-color: #fff !important;
+}
+
+.codes pre {
+ padding: 20px !important;
+ font-size: 13.6px;
+ line-height: 1.8;
+}
+
+.react-syntax-highlighter-line-number {
+ opacity: 0.2;
+ pointer-events: none;
+ user-select: none;
+ font-size: 12px;
+}
+
+pre,
+code {
+ font-family: 'Space Mono', monospace;
+ font-variant-ligatures: no-contextual;
+}
+
+.view.show-code iframe {
+ display: none;
+}
+
+.view.show-code .codes {
+ display: block;
+}
+
+.github {
+ padding: 8px 12px;
+ border-radius: 50px;
+ position: fixed;
+ right: 20px;
+ bottom: 20px;
+ background-color: var(--solid-900);
+ color: #fff;
+ display: inline-flex;
+ font-weight: 600;
+ align-items: center;
+ justify-content: center;
+}
+
+.github svg {
+ width: 24px;
+ flex-shrink: 0;
+ margin-right: 6px;
+}
+
+.dark-mode .github {
+ color: #1a202c;
+ background-color: #fff;
+}
+
+.clipboard-wrapper {
+ display: flex;
+ flex-direction: row-reverse;
+ align-items: center;
+ position: relative;
+}
+
+.clipboard-tooltip {
+ color: #68717d;
+ font-size: 13px;
+ background-color: #fff;
+ padding: 2px 5px;
+ letter-spacing: 0.2px;
+ border-radius: 4px;
+ position: absolute;
+ right: 100%;
+ opacity: 0;
+ transition: opacity 0.4s, margin-right 0.4s;
+}
+
+.clipboard-tooltip.is-copied {
+ margin-right: 6px;
+ opacity: 1;
+}
+
+@media (max-width: 1060px) {
+ .device {
+ display: none;
+ }
+
+ .switcher {
+ margin-right: 0;
+ }
+}
+
+.keyboard-nav {
+ position: fixed;
+ right: 158px;
+ bottom: 18px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.keyboard-nav-row {
+ display: flex;
+}
+
+.keyboard-button {
+ width: 20px;
+ height: 20px;
+ opacity: 1;
+ border-radius: 4px;
+ flex-shrink: 0;
+ margin: 1px;
+ opacity: 0.5;
+ background-color: var(--base-300);
+ color: var(--solid-900);
+ padding: 3px;
+ position: relative;
+}
+
+.keyboard-nav:hover .keyboard-button:not(:hover) {
+ opacity: 0.2;
+}
+
+.keyboard-button:hover {
+ opacity: 1;
+}
+
+.keyboard-button:before {
+ content: attr(data-info);
+ position: absolute;
+ color: var(--solid-900);
+ pointer-events: none;
+ font-size: 8px;
+ text-transform: uppercase;
+ transition: opacity 0.3s;
+ text-align: center;
+ white-space: nowrap;
+ padding: 1px 4px;
+ background-color: var(--base-200);
+ border-radius: 4px;
+ opacity: 0;
+ pointer-events: none;
+ z-index: -1;
+}
+
+.keyboard-button.is-active {
+ animation-name: keyboard;
+ animation-duration: 0.2s;
+ animation-fill-mode: forwards;
+}
+
+@keyframes keyboard {
+ 0% {
+ box-shadow: 0;
+ }
+ 50% {
+ box-shadow: 0 0 0 5px var(--main-500);
+ z-index: 1;
+ }
+}
+
+.keyboard-button:hover:before {
+ opacity: 1;
+ z-index: 1;
+}
+
+.k-up:before {
+ left: 50%;
+ transform: translateX(-50%);
+ top: 24px;
+}
+
+.k-down:before {
+ left: 50%;
+ transform: translateX(-50%);
+ top: -18px;
+}
+
+.k-left:before {
+ right: -70px;
+ top: 4px;
+}
+
+.k-right:before {
+ left: -76px;
+ top: 4px;
+}
+
+.keyboard-button:hover:before {
+ opacity: 1;
+}
+
+@media (max-width: 960px) {
+ .copy-to-clipboard {
+ display: none;
+ }
+
+ .keyboard-nav {
+ display: none;
+ }
+}
+
+@media (max-width: 760px) {
+ .copy-the-block {
+ display: none;
+ }
+
+ .opener {
+ width: 40px;
+ height: 100%;
+ overflow: hidden;
+ color: transparent;
+ white-space: nowrap;
+ }
+
+ .opener::before {
+ width: 24px;
+ height: 24px;
+ }
+
+ .mode {
+ margin-left: auto;
+ }
+
+ .sidebar {
+ z-index: 2;
+ }
+
+ .switcher {
+ position: fixed;
+ bottom: 24px;
+ z-index: 1;
+ background-color: #fff;
+ border: 1px solid var(--base-200);
+ left: 20px;
+ }
+
+ .dark-mode .switcher {
+ background-color: #1a202c;
+ }
+}
+
+@media (max-width: 380px) {
+ .github {
+ width: 36px;
+ height: 36px;
+ overflow: hidden;
+ border-radius: 50%;
+ color: transparent;
+ white-space: nowrap;
+ }
+
+ .github svg {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ color: #fff;
+ transform: translate(-50%, -50%);
+ }
+
+ .dark-mode .github {
+ color: transparent;
+ }
+
+ .dark-mode .github svg {
+ color: #1a202c;
+ }
+}
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index 67e93c2..55ccb01 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -12,7 +12,9 @@ require('channels')
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below.
//
-// const images = require.context('../images', true)
-// const imagePath = (name) => images(name, true)
+const images = require.context('../images', true)
+const imagePath = name => images(name, true)
import 'controllers'
+
+require('typeface-raleway')
diff --git a/app/javascript/packs/application.scss b/app/javascript/packs/application.scss
deleted file mode 100644
index a31e444..0000000
--- a/app/javascript/packs/application.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-@import 'tailwindcss/base';
-@import 'tailwindcss/components';
-@import 'tailwindcss/utilities';
diff --git a/app/models/badge.rb b/app/models/badge.rb
new file mode 100644
index 0000000..cc19d6f
--- /dev/null
+++ b/app/models/badge.rb
@@ -0,0 +1,45 @@
+# == Schema Information
+#
+# Table name: badges
+#
+# id :bigint not null, primary key
+# asset_url :string
+# description :string
+# source_url :string
+# title :string not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# organization_id :bigint
+#
+# Indexes
+#
+# index_badges_on_organization_id (organization_id)
+#
+class Badge < ApplicationRecord
+ # extends ...................................................................
+
+ # includes ..................................................................
+
+ # relationships .............................................................
+ belongs_to :organization
+ has_many :categorizations, as: :categorizable
+
+ # validations ...............................................................
+ validates :title, presence: true
+ validates :description, length: {maximum: 100}
+ validates_uniqueness_of :asset_url, scope: :title
+
+ # callbacks .................................................................
+
+ # scopes ....................................................................
+
+ # additional config (i.e. accepts_nested_attribute_for etc...) ..............
+
+ # class methods .............................................................
+
+ # public instance methods ...................................................
+
+ # protected instance methods ................................................
+
+ # private instance methods ..................................................
+end
diff --git a/app/models/categorization.rb b/app/models/categorization.rb
new file mode 100644
index 0000000..9c929cb
--- /dev/null
+++ b/app/models/categorization.rb
@@ -0,0 +1,46 @@
+# == Schema Information
+#
+# Table name: categorizations
+#
+# id :bigint not null, primary key
+# categorizable_type :string not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# categorizable_id :bigint not null
+# category_id :bigint not null
+#
+# Indexes
+#
+# categorizationable_id_type_idx (categorizable_id,categorizable_type)
+# index_categorizations_on_categorizable_id (categorizable_id)
+# index_categorizations_on_categorizable_type (categorizable_type)
+# index_categorizations_on_category_id (category_id)
+#
+class Categorization < ApplicationRecord
+ # extends ...................................................................
+
+ # includes ..................................................................
+
+ # relationships .............................................................
+ belongs_to :category
+ belongs_to :categorizable, polymorphic: true
+
+ # validations ...............................................................
+ validates :category, presence: true
+ validates :categorizable_type, presence: true
+ validates :categorizable_id, presence: true
+
+ # callbacks .................................................................
+
+ # scopes ....................................................................
+
+ # additional config (i.e. accepts_nested_attribute_for etc...) ..............
+
+ # class methods .............................................................
+
+ # public instance methods ...................................................
+
+ # protected instance methods ................................................
+
+ # private instance methods ..................................................
+end
diff --git a/app/models/category.rb b/app/models/category.rb
new file mode 100644
index 0000000..3710586
--- /dev/null
+++ b/app/models/category.rb
@@ -0,0 +1,40 @@
+# == Schema Information
+#
+# Table name: categories
+#
+# id :bigint not null, primary key
+# categorizations_count :integer default(0), not null
+# name :string not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_categories_on_name (name)
+#
+class Category < ApplicationRecord
+ # extends ...................................................................
+
+ # includes ..................................................................
+
+ # relationships .............................................................
+ has_many :categorizations, dependent: :destroy, class_name: "Categorization", as: :categorizable
+
+ # validations ...............................................................
+ validates :name, length: {in: 1..40}, presence: true
+ validates :categorizations_count, numericality: true
+
+ # callbacks .................................................................
+
+ # scopes ....................................................................
+
+ # additional config (i.e. accepts_nested_attribute_for etc...) ..............
+
+ # class methods .............................................................
+
+ # public instance methods ...................................................
+
+ # protected instance methods ................................................
+
+ # private instance methods ..................................................
+end
diff --git a/app/models/organization.rb b/app/models/organization.rb
new file mode 100644
index 0000000..89b1c72
--- /dev/null
+++ b/app/models/organization.rb
@@ -0,0 +1,40 @@
+# == Schema Information
+#
+# Table name: organizations
+#
+# id :bigint not null, primary key
+# company :boolean default(FALSE), not null
+# name :string not null
+# pricing_strategy :integer default("free"), not null
+# url :string
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+class Organization < ApplicationRecord
+ # extends ...................................................................
+
+ # includes ..................................................................
+
+ # relationships .............................................................
+ has_many :badges
+
+ # validations ...............................................................
+ validates :name, length: {in: 1..40}, presence: true
+ validates :company, presence: true
+ validates :pricing_strategy, presence: true
+
+ # callbacks .................................................................
+
+ # scopes ....................................................................
+
+ # additional config (i.e. accepts_nested_attribute_for etc...) ..............
+ enum pricing_strategy: {free: 0, hybrid: 1, paid: 2}
+
+ # class methods .............................................................
+
+ # public instance methods ...................................................
+
+ # protected instance methods ................................................
+
+ # private instance methods ..................................................
+end
diff --git a/app/views/badges/_badge.json.jbuilder b/app/views/badges/_badge.json.jbuilder
new file mode 100644
index 0000000..25d5ce8
--- /dev/null
+++ b/app/views/badges/_badge.json.jbuilder
@@ -0,0 +1,2 @@
+json.extract! badge, :id, :title, :asset_url, :source_url, :belongs_to, :created_at, :updated_at
+json.url badge_url(badge, format: :json)
diff --git a/app/views/badges/_form.html.erb b/app/views/badges/_form.html.erb
new file mode 100644
index 0000000..1f3cf92
--- /dev/null
+++ b/app/views/badges/_form.html.erb
@@ -0,0 +1,18 @@
+<%= form_with(model: badge, local: true) do |form| %>
+ <%= render(Form::ErrorsComponent.new(object: badge)) %>
+
+ <%= form.text_field :title, class: "w-full bg-gray-800 rounded border border-gray-700 text-white focus:outline-none focus:border-red-500 text-base px-4 py-2", placeholder: "Title" %>
+
+
+ <%= form.text_area :description, class: "block w-full h-48 px-4 py-2 text-base text-white bg-gray-800 border border-gray-700 rounded resize-none focus:outline-none focus:border-red-500", placeholder: "Description" %>
+
+
+ <%= form.text_field :source_url, class: "w-full bg-gray-800 rounded border border-gray-700 text-white focus:outline-none focus:border-red-500 text-base px-4 py-2", placeholder: "Source URL" %>
+
+
+ <%= form.text_field :asset_url, class: "w-full bg-gray-800 rounded border border-gray-700 text-white focus:outline-none focus:border-red-500 text-base px-4 py-2", placeholder: "Asset URL" %>
+
+
+ <%= form.submit "Create Badge", class: "flex text-white bg-red-500 border-0 py-2 px-8 focus:outline-none hover:bg-red-600 rounded text-lg" %>
+
+<% end %>
diff --git a/app/views/badges/edit.html.erb b/app/views/badges/edit.html.erb
new file mode 100644
index 0000000..abe777e
--- /dev/null
+++ b/app/views/badges/edit.html.erb
@@ -0,0 +1,7 @@
+<%= render(PageComponent.new) do |component| %>
+ <%= component.with(:title) {"Editing Badge"} %>
+ <% component.with(:body) do %>
+ <%= render 'form', badge: @badge %>
+ <%= link_to 'Show', @badge %> | <%= link_to 'Back', badges_path %>
+ <% end %>
+<% end %>
diff --git a/app/views/badges/index.html.erb b/app/views/badges/index.html.erb
new file mode 100644
index 0000000..ecb1f8b
--- /dev/null
+++ b/app/views/badges/index.html.erb
@@ -0,0 +1,13 @@
+<%= notice %>
+
+<%= render(PageComponent.new) do |component| %>
+ <%= component.with(:title) {"Badges"} %>
+ <% component.with(:body) do %>
+ <%= render(BadgeComponent.with_collection(@badges)) %>
+ <% end %>
+ <% component.with(:actions) do %>
+
+ <%= link_to 'New Badge', new_badge_path, class: "mx-auto mt-16 text-white bg-red-500 border-0 py-2 px-8 focus:outline-none hover:bg-red-600 rounded text-lg" %>
+
+ <% end %>
+<% end %>
diff --git a/app/views/badges/index.json.jbuilder b/app/views/badges/index.json.jbuilder
new file mode 100644
index 0000000..48b4254
--- /dev/null
+++ b/app/views/badges/index.json.jbuilder
@@ -0,0 +1 @@
+json.array! @badges, partial: "badges/badge", as: :badge
diff --git a/app/views/badges/new.html.erb b/app/views/badges/new.html.erb
new file mode 100644
index 0000000..2e904b3
--- /dev/null
+++ b/app/views/badges/new.html.erb
@@ -0,0 +1,7 @@
+<%= render(PageComponent.new) do |component| %>
+ <%= component.with(:title) {"New Badge"} %>
+ <% component.with(:body) do %>
+ <%= render 'form', badge: @badge %>
+ <%= link_to 'Back', badges_path %>
+ <% end %>
+<% end %>
diff --git a/app/views/badges/show.html.erb b/app/views/badges/show.html.erb
new file mode 100644
index 0000000..3166acc
--- /dev/null
+++ b/app/views/badges/show.html.erb
@@ -0,0 +1,24 @@
+<%= notice %>
+
+
+ Title:
+ <%= @badge.title %>
+
+
+
+ Asset url:
+ <%= @badge.asset_url %>
+
+
+
+ Source url:
+ <%= @badge.source_url %>
+
+
+
+ Belongs to:
+ <%= @badge.belongs_to %>
+
+
+<%= link_to 'Edit', edit_badge_path(@badge) %> |
+<%= link_to 'Back', badges_path %>
diff --git a/app/views/badges/show.json.jbuilder b/app/views/badges/show.json.jbuilder
new file mode 100644
index 0000000..e4cfb7c
--- /dev/null
+++ b/app/views/badges/show.json.jbuilder
@@ -0,0 +1 @@
+json.partial! "badges/badge", badge: @badge
diff --git a/app/views/categories/_category.json.jbuilder b/app/views/categories/_category.json.jbuilder
new file mode 100644
index 0000000..add821b
--- /dev/null
+++ b/app/views/categories/_category.json.jbuilder
@@ -0,0 +1,2 @@
+json.extract! category, :id, :name, :categorizations_count, :created_at, :updated_at
+json.url category_url(category, format: :json)
diff --git a/app/views/categories/_form.html.erb b/app/views/categories/_form.html.erb
new file mode 100644
index 0000000..8be6545
--- /dev/null
+++ b/app/views/categories/_form.html.erb
@@ -0,0 +1,27 @@
+<%= form_with(model: category, local: true) do |form| %>
+ <% if category.errors.any? %>
+
+
<%= pluralize(category.errors.count, "error") %> prohibited this category from being saved:
+
+
+ <% category.errors.full_messages.each do |message| %>
+ - <%= message %>
+ <% end %>
+
+
+ <% end %>
+
+
+ <%= form.label :name %>
+ <%= form.text_field :name %>
+
+
+
+ <%= form.label :categorizations_count %>
+ <%= form.text_field :categorizations_count %>
+
+
+
+ <%= form.submit %>
+
+<% end %>
diff --git a/app/views/categories/edit.html.erb b/app/views/categories/edit.html.erb
new file mode 100644
index 0000000..b969417
--- /dev/null
+++ b/app/views/categories/edit.html.erb
@@ -0,0 +1,6 @@
+Editing Category
+
+<%= render 'form', category: @category %>
+
+<%= link_to 'Show', @category %> |
+<%= link_to 'Back', categories_path %>
diff --git a/app/views/categories/index.html.erb b/app/views/categories/index.html.erb
new file mode 100644
index 0000000..43c7b09
--- /dev/null
+++ b/app/views/categories/index.html.erb
@@ -0,0 +1,29 @@
+<%= notice %>
+
+Categories
+
+
+
+
+ Name |
+ Categorizations count |
+ |
+
+
+
+
+ <% @categories.each do |category| %>
+
+ <%= category.name %> |
+ <%= category.categorizations_count %> |
+ <%= link_to 'Show', category %> |
+ <%= link_to 'Edit', edit_category_path(category) %> |
+ <%= link_to 'Destroy', category, method: :delete, data: { confirm: 'Are you sure?' } %> |
+
+ <% end %>
+
+
+
+
+
+<%= link_to 'New Category', new_category_path %>
diff --git a/app/views/categories/index.json.jbuilder b/app/views/categories/index.json.jbuilder
new file mode 100644
index 0000000..aa5baad
--- /dev/null
+++ b/app/views/categories/index.json.jbuilder
@@ -0,0 +1 @@
+json.array! @categories, partial: "categories/category", as: :category
diff --git a/app/views/categories/new.html.erb b/app/views/categories/new.html.erb
new file mode 100644
index 0000000..91d5ef7
--- /dev/null
+++ b/app/views/categories/new.html.erb
@@ -0,0 +1,5 @@
+New Category
+
+<%= render 'form', category: @category %>
+
+<%= link_to 'Back', categories_path %>
diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb
new file mode 100644
index 0000000..5daa610
--- /dev/null
+++ b/app/views/categories/show.html.erb
@@ -0,0 +1,14 @@
+<%= notice %>
+
+
+ Name:
+ <%= @category.name %>
+
+
+
+ Categorizations count:
+ <%= @category.categorizations_count %>
+
+
+<%= link_to 'Edit', edit_category_path(@category) %> |
+<%= link_to 'Back', categories_path %>
diff --git a/app/views/categories/show.json.jbuilder b/app/views/categories/show.json.jbuilder
new file mode 100644
index 0000000..30e6b47
--- /dev/null
+++ b/app/views/categories/show.json.jbuilder
@@ -0,0 +1 @@
+json.partial! "categories/category", category: @category
diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb
index f77700b..9bffc14 100644
--- a/app/views/home/index.html.erb
+++ b/app/views/home/index.html.erb
@@ -1 +1,9 @@
-Home#index
+
+
+
+ Rubyists.dev
+
+
+ <%= link_to "Badges", badges_path, class: "flex-shrink-0 px-8 py-2 mt-10 text-lg text-white bg-red-500 border-0 rounded focus:outline-none hover:bg-red-600 sm:mt-0" %>
+
+
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index ff26828..2f8f7be 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -5,18 +5,19 @@
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= action_cable_meta_tag %>
- <%= stylesheet_pack_tag "application", media: "all", "data-turbolinks-track": "reload" %>
<%= javascript_pack_tag "application", "data-turbolinks-track": "reload", defer: true %>
+ <%= stylesheet_pack_tag "application", media: "all", "data-turbolinks-track": "reload" %>
- <% flash.each do |msg_type, message| %>
-
-
-
- <%= message %>
-
-
- <% end %>
- <%= yield %>
+
+ <%= render(HeaderComponent.new) %>
+ <%= render(FlashComponent.new(flash: flash)) %>
+
+
+
+ <%= render(FooterComponent.new) %>
+