diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000..6a703d4059 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..e08e2d0cc4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +/node_modules +/yarn-error.log + +.byebug_history +.env + +/coverage +coverage diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000000..cfda5e0270 --- /dev/null +++ b/Gemfile @@ -0,0 +1,72 @@ +source 'https://rubygems.org' + +git_source(:github) do |repo_name| + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") + "https://github.com/#{repo_name}.git" +end + +# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' +gem 'rails', '~> 5.1.4' +# Use postgresql as the database for Active Record +gem 'pg', '~> 0.18' +# Use Puma as the app server +gem 'puma', '~> 3.7' +# Use SCSS for stylesheets +gem 'sass-rails', '~> 5.0' +# Use Uglifier as compressor for JavaScript assets +gem 'uglifier', '>= 1.3.0' +# See https://github.com/rails/execjs#readme for more supported runtimes +# gem 'therubyracer', platforms: :ruby +gem 'simplecov', :require => false, :group => :test + + +#added these gems because the terminal told me to +ruby '2.4.1' +# Use CoffeeScript for .coffee assets and views +# gem 'coffee-rails', '~> 4.2' +# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks +gem 'turbolinks', '~> 5' +# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder +gem 'jbuilder', '~> 2.5' +# Use Redis adapter to run Action Cable in production +# gem 'redis', '~> 3.0' +# Use ActiveModel has_secure_password +# gem 'bcrypt', '~> 3.1.7' +gem "omniauth" +gem "omniauth-github" +# Use Capistrano for deployment +# gem 'capistrano-rails', group: :development + +group :development, :test do + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] + # Adds support for Capybara system testing and selenium driver + gem 'capybara', '~> 2.13' + gem 'selenium-webdriver' + gem 'pry-rails' +end + +group :development do + # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. + gem 'web-console', '>= 3.3.0' + gem 'listen', '>= 3.0.5', '< 3.2' + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' + gem 'spring-watcher-listen', '~> 2.0.0' + gem 'dotenv-rails' +end + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] + +gem 'jquery-turbolinks' +gem 'foundation-rails', '6.4.1.2' +group :development do + gem 'better_errors' + gem 'binding_of_caller' +end + +group :test do + gem 'minitest-rails' + gem 'minitest-reporters' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000000..a2d2761d30 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,271 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.1.4) + actionpack (= 5.1.4) + nio4r (~> 2.0) + websocket-driver (~> 0.6.1) + actionmailer (5.1.4) + actionpack (= 5.1.4) + actionview (= 5.1.4) + activejob (= 5.1.4) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.1.4) + actionview (= 5.1.4) + activesupport (= 5.1.4) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.1.4) + activesupport (= 5.1.4) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.1.4) + activesupport (= 5.1.4) + globalid (>= 0.3.6) + activemodel (5.1.4) + activesupport (= 5.1.4) + activerecord (5.1.4) + activemodel (= 5.1.4) + activesupport (= 5.1.4) + arel (~> 8.0) + activesupport (5.1.4) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (~> 0.7) + minitest (~> 5.1) + tzinfo (~> 1.1) + addressable (2.5.2) + public_suffix (>= 2.0.2, < 4.0) + ansi (1.5.0) + arel (8.0.0) + babel-source (5.8.35) + babel-transpiler (0.7.0) + babel-source (>= 4.0, < 6) + execjs (~> 2.0) + better_errors (2.4.0) + coderay (>= 1.0.0) + erubi (>= 1.0.0) + rack (>= 0.9.0) + bindex (0.5.0) + binding_of_caller (0.7.2) + debug_inspector (>= 0.0.1) + builder (3.2.3) + byebug (9.1.0) + capybara (2.15.4) + addressable + mini_mime (>= 0.1.3) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) + childprocess (0.8.0) + ffi (~> 1.0, >= 1.0.11) + coderay (1.1.2) + concurrent-ruby (1.0.5) + crass (1.0.2) + debug_inspector (0.0.3) + docile (1.1.5) + dotenv (2.2.1) + dotenv-rails (2.2.1) + dotenv (= 2.2.1) + railties (>= 3.2, < 5.2) + erubi (1.7.0) + execjs (2.7.0) + faraday (0.12.2) + multipart-post (>= 1.2, < 3) + ffi (1.9.18) + foundation-rails (6.4.1.2) + railties (>= 3.1.0) + sass (>= 3.3.0, < 3.5) + sprockets-es6 (>= 0.9.0) + globalid (0.4.0) + activesupport (>= 4.2.0) + hashie (3.5.6) + i18n (0.9.0) + concurrent-ruby (~> 1.0) + jbuilder (2.7.0) + activesupport (>= 4.2.0) + multi_json (>= 1.2) + jquery-turbolinks (2.1.0) + railties (>= 3.1.0) + turbolinks + json (2.0.2) + jwt (1.5.6) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + loofah (2.1.1) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.6.6) + mime-types (>= 1.16, < 4) + method_source (0.9.0) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mini_mime (0.1.4) + mini_portile2 (2.3.0) + minitest (5.10.3) + minitest-rails (3.0.0) + minitest (~> 5.8) + railties (~> 5.0) + minitest-reporters (1.1.18) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + multi_json (1.12.2) + multi_xml (0.6.0) + multipart-post (2.0.0) + nio4r (2.1.0) + nokogiri (1.8.1) + mini_portile2 (~> 2.3.0) + oauth2 (1.4.0) + faraday (>= 0.8, < 0.13) + jwt (~> 1.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + omniauth (1.7.1) + hashie (>= 3.4.6, < 3.6.0) + rack (>= 1.6.2, < 3) + omniauth-github (1.3.0) + omniauth (~> 1.5) + omniauth-oauth2 (>= 1.4.0, < 2.0) + omniauth-oauth2 (1.4.0) + oauth2 (~> 1.0) + omniauth (~> 1.2) + pg (0.21.0) + pry (0.11.1) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry-rails (0.3.6) + pry (>= 0.10.4) + public_suffix (3.0.0) + puma (3.10.0) + rack (2.0.3) + rack-test (0.7.0) + rack (>= 1.0, < 3) + rails (5.1.4) + actioncable (= 5.1.4) + actionmailer (= 5.1.4) + actionpack (= 5.1.4) + actionview (= 5.1.4) + activejob (= 5.1.4) + activemodel (= 5.1.4) + activerecord (= 5.1.4) + activesupport (= 5.1.4) + bundler (>= 1.3.0) + railties (= 5.1.4) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.3) + loofah (~> 2.0) + railties (5.1.4) + actionpack (= 5.1.4) + activesupport (= 5.1.4) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (12.1.0) + rb-fsevent (0.10.2) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + ruby-progressbar (1.9.0) + ruby_dep (1.5.0) + rubyzip (1.2.1) + sass (3.4.25) + sass-rails (5.0.6) + railties (>= 4.0.0, < 6) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + selenium-webdriver (3.6.0) + childprocess (~> 0.5) + rubyzip (~> 1.0) + simplecov (0.15.0) + docile (~> 1.1.0) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.2) + spring (2.0.2) + activesupport (>= 4.2) + spring-watcher-listen (2.0.1) + listen (>= 2.7, < 4.0) + spring (>= 1.2, < 3.0) + sprockets (3.7.1) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-es6 (0.9.2) + babel-source (>= 5.8.11) + babel-transpiler + sprockets (>= 3.0.0) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + thor (0.20.0) + thread_safe (0.3.6) + tilt (2.0.8) + turbolinks (5.0.1) + turbolinks-source (~> 5) + turbolinks-source (5.0.3) + tzinfo (1.2.3) + thread_safe (~> 0.1) + uglifier (3.2.0) + execjs (>= 0.3.0, < 3) + web-console (3.5.1) + actionview (>= 5.0) + activemodel (>= 5.0) + bindex (>= 0.4.0) + railties (>= 5.0) + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.2) + xpath (2.1.0) + nokogiri (~> 1.3) + +PLATFORMS + ruby + +DEPENDENCIES + better_errors + binding_of_caller + byebug + capybara (~> 2.13) + dotenv-rails + foundation-rails (= 6.4.1.2) + jbuilder (~> 2.5) + jquery-turbolinks + listen (>= 3.0.5, < 3.2) + minitest-rails + minitest-reporters + omniauth + omniauth-github + pg (~> 0.18) + pry-rails + puma (~> 3.7) + rails (~> 5.1.4) + sass-rails (~> 5.0) + selenium-webdriver + simplecov + spring + spring-watcher-listen (~> 2.0.0) + turbolinks (~> 5) + tzinfo-data + uglifier (>= 1.3.0) + web-console (>= 3.3.0) + +RUBY VERSION + ruby 2.4.1p111 + +BUNDLED WITH + 1.16.0.pre.3 diff --git a/Procfile b/Procfile new file mode 100644 index 0000000000..c2c566e8cc --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: bundle exec puma -C config/puma.rb diff --git a/README.md b/README.md index 1b10607db1..f1b8e0d044 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ +# Lore Store +Lore Store is a full stack Ruby on Rails web app developed by [@anemonekey](https://github.com/anemonekey), [@Guribot](https://github.com/Guribot), [@oxenran](https://github.com/oxenran), and [@sairagula](https://github.com/sairagula) for Ada Developers Academy Cohort 8. It utilizes PostgreSQL relational databases and Oauth user verification. + +### See the live website [here](http://lorestore.herokuapp.com/). + + +Original assignment text below. + +--- + # bEtsy [b]Etsy will be an online store where a wide variety of products can be listed and sold by any user. In this project we will focus on reinforcing the major components of Rails, Model Validation, as well as introducing some more complex logic such as user authentication. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000000..e85f913914 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative 'config/application' + +Rails.application.load_tasks diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 0000000000..b16e53d6d5 --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,3 @@ +//= link_tree ../images +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css diff --git a/app/assets/images/.keep b/app/assets/images/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/assets/images/001-zombie.png b/app/assets/images/001-zombie.png new file mode 100644 index 0000000000..47f11efeb0 Binary files /dev/null and b/app/assets/images/001-zombie.png differ diff --git a/app/assets/images/002-eye.png b/app/assets/images/002-eye.png new file mode 100644 index 0000000000..c0b1c9057a Binary files /dev/null and b/app/assets/images/002-eye.png differ diff --git a/app/assets/images/003-pumpkin.png b/app/assets/images/003-pumpkin.png new file mode 100644 index 0000000000..637c680dda Binary files /dev/null and b/app/assets/images/003-pumpkin.png differ diff --git a/app/assets/images/004-torch.png b/app/assets/images/004-torch.png new file mode 100644 index 0000000000..a94e4869a7 Binary files /dev/null and b/app/assets/images/004-torch.png differ diff --git a/app/assets/images/005-trident.png b/app/assets/images/005-trident.png new file mode 100644 index 0000000000..f50f6af77d Binary files /dev/null and b/app/assets/images/005-trident.png differ diff --git a/app/assets/images/006-poison.png b/app/assets/images/006-poison.png new file mode 100644 index 0000000000..2cf0435f82 Binary files /dev/null and b/app/assets/images/006-poison.png differ diff --git a/app/assets/images/007-castle.png b/app/assets/images/007-castle.png new file mode 100644 index 0000000000..3ce7c0f9ce Binary files /dev/null and b/app/assets/images/007-castle.png differ diff --git a/app/assets/images/008-spellbook.png b/app/assets/images/008-spellbook.png new file mode 100644 index 0000000000..e292620ae5 Binary files /dev/null and b/app/assets/images/008-spellbook.png differ diff --git a/app/assets/images/009-candelabra.png b/app/assets/images/009-candelabra.png new file mode 100644 index 0000000000..457c971825 Binary files /dev/null and b/app/assets/images/009-candelabra.png differ diff --git a/app/assets/images/010-candle.png b/app/assets/images/010-candle.png new file mode 100644 index 0000000000..be988694a9 Binary files /dev/null and b/app/assets/images/010-candle.png differ diff --git a/app/assets/images/011-tree.png b/app/assets/images/011-tree.png new file mode 100644 index 0000000000..7f12378f0f Binary files /dev/null and b/app/assets/images/011-tree.png differ diff --git a/app/assets/images/012-owl.png b/app/assets/images/012-owl.png new file mode 100644 index 0000000000..4b0afea074 Binary files /dev/null and b/app/assets/images/012-owl.png differ diff --git a/app/assets/images/013-bat.png b/app/assets/images/013-bat.png new file mode 100644 index 0000000000..27bb3cc6c7 Binary files /dev/null and b/app/assets/images/013-bat.png differ diff --git a/app/assets/images/014-coffin.png b/app/assets/images/014-coffin.png new file mode 100644 index 0000000000..5f8e014a0a Binary files /dev/null and b/app/assets/images/014-coffin.png differ diff --git a/app/assets/images/015-broom.png b/app/assets/images/015-broom.png new file mode 100644 index 0000000000..fbf1e4f6fb Binary files /dev/null and b/app/assets/images/015-broom.png differ diff --git a/app/assets/images/016-skull.png b/app/assets/images/016-skull.png new file mode 100644 index 0000000000..67fa57a0f2 Binary files /dev/null and b/app/assets/images/016-skull.png differ diff --git a/app/assets/images/017-potion.png b/app/assets/images/017-potion.png new file mode 100644 index 0000000000..7ed9e31d84 Binary files /dev/null and b/app/assets/images/017-potion.png differ diff --git a/app/assets/images/018-potful.png b/app/assets/images/018-potful.png new file mode 100644 index 0000000000..15a1c48e58 Binary files /dev/null and b/app/assets/images/018-potful.png differ diff --git a/app/assets/images/019-crescent-moon.png b/app/assets/images/019-crescent-moon.png new file mode 100644 index 0000000000..1bca6d3e83 Binary files /dev/null and b/app/assets/images/019-crescent-moon.png differ diff --git a/app/assets/images/020-full-moon.png b/app/assets/images/020-full-moon.png new file mode 100644 index 0000000000..5d47ed99b3 Binary files /dev/null and b/app/assets/images/020-full-moon.png differ diff --git a/app/assets/images/021-scythe.png b/app/assets/images/021-scythe.png new file mode 100644 index 0000000000..eb09aa8ad2 Binary files /dev/null and b/app/assets/images/021-scythe.png differ diff --git a/app/assets/images/022-cleaver.png b/app/assets/images/022-cleaver.png new file mode 100644 index 0000000000..3e509214c2 Binary files /dev/null and b/app/assets/images/022-cleaver.png differ diff --git a/app/assets/images/023-knife.png b/app/assets/images/023-knife.png new file mode 100644 index 0000000000..f26b9c8032 Binary files /dev/null and b/app/assets/images/023-knife.png differ diff --git a/app/assets/images/024-axe.png b/app/assets/images/024-axe.png new file mode 100644 index 0000000000..d261da8138 Binary files /dev/null and b/app/assets/images/024-axe.png differ diff --git a/app/assets/images/025-grave.png b/app/assets/images/025-grave.png new file mode 100644 index 0000000000..dd3d490528 Binary files /dev/null and b/app/assets/images/025-grave.png differ diff --git a/app/assets/images/026-gravestone.png b/app/assets/images/026-gravestone.png new file mode 100644 index 0000000000..b750f1f305 Binary files /dev/null and b/app/assets/images/026-gravestone.png differ diff --git a/app/assets/images/027-eye-jar.png b/app/assets/images/027-eye-jar.png new file mode 100644 index 0000000000..8d08ff72c0 Binary files /dev/null and b/app/assets/images/027-eye-jar.png differ diff --git a/app/assets/images/028-heart.png b/app/assets/images/028-heart.png new file mode 100644 index 0000000000..e7a2719070 Binary files /dev/null and b/app/assets/images/028-heart.png differ diff --git a/app/assets/images/029-ghost.png b/app/assets/images/029-ghost.png new file mode 100644 index 0000000000..66bbd1d245 Binary files /dev/null and b/app/assets/images/029-ghost.png differ diff --git a/app/assets/images/030-brain.png b/app/assets/images/030-brain.png new file mode 100644 index 0000000000..0cde3fa806 Binary files /dev/null and b/app/assets/images/030-brain.png differ diff --git a/app/assets/images/hero.jpeg b/app/assets/images/hero.jpeg new file mode 100644 index 0000000000..180bcfe9db Binary files /dev/null and b/app/assets/images/hero.jpeg differ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 0000000000..4f2cc0f55a --- /dev/null +++ b/app/assets/javascripts/application.js @@ -0,0 +1,18 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's +// vendor/assets/javascripts directory can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. JavaScript code in this file should be added after the last require_* statement. +// +// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details +// about supported directives. +// +//= require rails-ujs +//= require foundation +//= require turbolinks +//= require_tree . + +$(function(){ $(document).foundation(); }); diff --git a/app/assets/javascripts/cable.js b/app/assets/javascripts/cable.js new file mode 100644 index 0000000000..739aa5f022 --- /dev/null +++ b/app/assets/javascripts/cable.js @@ -0,0 +1,13 @@ +// Action Cable provides the framework to deal with WebSockets in Rails. +// You can generate new channels where WebSocket features live using the `rails generate channel` command. +// +//= require action_cable +//= require_self +//= require_tree ./channels + +(function() { + this.App || (this.App = {}); + + App.cable = ActionCable.createConsumer(); + +}).call(this); diff --git a/app/assets/javascripts/categories.js b/app/assets/javascripts/categories.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/categories.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/channels/.keep b/app/assets/javascripts/channels/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/main.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/merchants.js b/app/assets/javascripts/merchants.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/merchants.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/order_products.js b/app/assets/javascripts/order_products.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/order_products.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/orders.js b/app/assets/javascripts/orders.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/orders.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/products.js b/app/assets/javascripts/products.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/products.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/reviews.js b/app/assets/javascripts/reviews.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/reviews.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/stylesheets/_settings.scss b/app/assets/stylesheets/_settings.scss new file mode 100644 index 0000000000..f58c37fbb1 --- /dev/null +++ b/app/assets/stylesheets/_settings.scss @@ -0,0 +1,872 @@ +// Foundation for Sites Settings +// ----------------------------- +// +// Table of Contents: +// +// 1. Global +// 2. Breakpoints +// 3. The Grid +// 4. Base Typography +// 5. Typography Helpers +// 6. Abide +// 7. Accordion +// 8. Accordion Menu +// 9. Badge +// 10. Breadcrumbs +// 11. Button +// 12. Button Group +// 13. Callout +// 14. Card +// 15. Close Button +// 16. Drilldown +// 17. Dropdown +// 18. Dropdown Menu +// 19. Flexbox Utilities +// 20. Forms +// 21. Label +// 22. Media Object +// 23. Menu +// 24. Meter +// 25. Off-canvas +// 26. Orbit +// 27. Pagination +// 28. Progress Bar +// 29. Prototype Arrow +// 30. Prototype Border-Box +// 31. Prototype Border-None +// 32. Prototype Bordered +// 33. Prototype Display +// 34. Prototype Font-Styling +// 35. Prototype List-Style-Type +// 36. Prototype Overflow +// 37. Prototype Position +// 38. Prototype Rounded +// 39. Prototype Separator +// 40. Prototype Shadow +// 41. Prototype Sizing +// 42. Prototype Spacing +// 43. Prototype Text-Decoration +// 44. Prototype Text-Transformation +// 45. Prototype Text-Utilities +// 46. Responsive Embed +// 47. Reveal +// 48. Slider +// 49. Switch +// 50. Table +// 51. Tabs +// 52. Thumbnail +// 53. Title Bar +// 54. Tooltip +// 55. Top Bar +// 56. Xy Grid + +@import 'util/util'; + +// 1. Global +// --------- + +$global-font-size: 100%; +$global-width: rem-calc(1200); +$global-lineheight: 1.5; +$foundation-palette: ( + primary: #1779ba, + secondary: #767676, + success: #3adb76, + warning: #ffae00, + alert: #cc4b37, +); + +// Palette: +$pink: #DBC2CF; +$grey: #A7AAB9; +$gray: #A7AAB9; +$teal: #3C7A89; +$navy: #16262E; +$burgundy: #4F3640; + + +$light-gray: #e6e6e6; +$medium-gray: #cacaca; +$dark-gray: #8a8a8a; +$black: #0a0a0a; +$white: #fefefe; +$body-background: $white; +$body-font-color: $black; +$body-font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; +$body-antialiased: true; +$global-margin: 1rem; +$global-padding: 1rem; +$global-position: 1rem; +$global-weight-normal: normal; +$global-weight-bold: bold; +$global-radius: 0; +$global-menu-padding: 0.7rem 1rem; +$global-menu-nested-margin: 1rem; +$global-text-direction: ltr; +$global-flexbox: true; +$global-prototype-breakpoints: false; +$global-color-pick-contrast-tolerance: 0; +$print-transparent-backgrounds: true; + +@include add-foundation-colors; + +// 2. Breakpoints +// -------------- + +$breakpoints: ( + small: 0, + medium: 640px, + large: 1024px, + xlarge: 1200px, + xxlarge: 1440px, +); +$print-breakpoint: large; +$breakpoint-classes: (small medium large); + +// 3. The Grid +// ----------- + +$grid-row-width: $global-width; +$grid-column-count: 12; +$grid-column-gutter: ( + small: 20px, + medium: 30px, +); +$grid-column-align-edge: true; +$grid-column-alias: 'columns'; +$block-grid-max: 8; + +// 4. Base Typography +// ------------------ + +$header-font-family: $body-font-family; +$header-font-weight: $global-weight-normal; +$header-font-style: normal; +$font-family-monospace: Consolas, 'Liberation Mono', Courier, monospace; +$header-color: inherit; +$header-lineheight: 1.4; +$header-margin-bottom: 0.5rem; +$header-styles: ( + small: ( + 'h1': ('font-size': 24), + 'h2': ('font-size': 20), + 'h3': ('font-size': 19), + 'h4': ('font-size': 18), + 'h5': ('font-size': 17), + 'h6': ('font-size': 16), + ), + medium: ( + 'h1': ('font-size': 48), + 'h2': ('font-size': 40), + 'h3': ('font-size': 31), + 'h4': ('font-size': 25), + 'h5': ('font-size': 20), + 'h6': ('font-size': 16), + ), +); +$header-text-rendering: optimizeLegibility; +$small-font-size: 80%; +$header-small-font-color: $medium-gray; +$paragraph-lineheight: 1.6; +$paragraph-margin-bottom: 1rem; +$paragraph-text-rendering: optimizeLegibility; +$code-color: $black; +$code-font-family: $font-family-monospace; +$code-font-weight: $global-weight-normal; +$code-background: $light-gray; +$code-border: 1px solid $medium-gray; +$code-padding: rem-calc(2 5 1); +$anchor-color: $primary-color; +$anchor-color-hover: scale-color($anchor-color, $lightness: -14%); +$anchor-text-decoration: none; +$anchor-text-decoration-hover: none; +$hr-width: $global-width; +$hr-border: 1px solid $medium-gray; +$hr-margin: rem-calc(20) auto; +$list-lineheight: $paragraph-lineheight; +$list-margin-bottom: $paragraph-margin-bottom; +$list-style-type: disc; +$list-style-position: outside; +$list-side-margin: 1.25rem; +$list-nested-side-margin: 1.25rem; +$defnlist-margin-bottom: 1rem; +$defnlist-term-weight: $global-weight-bold; +$defnlist-term-margin-bottom: 0.3rem; +$blockquote-color: $dark-gray; +$blockquote-padding: rem-calc(9 20 0 19); +$blockquote-border: 1px solid $medium-gray; +$cite-font-size: rem-calc(13); +$cite-color: $dark-gray; +$cite-pseudo-content: '\2014 \0020'; +$keystroke-font: $font-family-monospace; +$keystroke-color: $black; +$keystroke-background: $light-gray; +$keystroke-padding: rem-calc(2 4 0); +$keystroke-radius: $global-radius; +$abbr-underline: 1px dotted $black; + +// 5. Typography Helpers +// --------------------- + +$lead-font-size: $global-font-size * 1.25; +$lead-lineheight: 1.6; +$subheader-lineheight: 1.4; +$subheader-color: $dark-gray; +$subheader-font-weight: $global-weight-normal; +$subheader-margin-top: 0.2rem; +$subheader-margin-bottom: 0.5rem; +$stat-font-size: 2.5rem; + +// 6. Abide +// -------- + +$abide-inputs: true; +$abide-labels: true; +$input-background-invalid: get-color(alert); +$form-label-color-invalid: get-color(alert); +$input-error-color: get-color(alert); +$input-error-font-size: rem-calc(12); +$input-error-font-weight: $global-weight-bold; + +// 7. Accordion +// ------------ + +$accordion-background: $white; +$accordion-plusminus: true; +$accordion-title-font-size: rem-calc(12); +$accordion-item-color: $primary-color; +$accordion-item-background-hover: $light-gray; +$accordion-item-padding: 1.25rem 1rem; +$accordion-content-background: $white; +$accordion-content-border: 1px solid $light-gray; +$accordion-content-color: $body-font-color; +$accordion-content-padding: 1rem; + +// 8. Accordion Menu +// ----------------- + +$accordionmenu-padding: $global-menu-padding; +$accordionmenu-nested-margin: $global-menu-nested-margin; +$accordionmenu-submenu-padding: $accordionmenu-padding; +$accordionmenu-arrows: true; +$accordionmenu-arrow-color: $primary-color; +$accordionmenu-item-background: null; +$accordionmenu-border: null; +$accordionmenu-submenu-toggle-background: null; +$accordion-submenu-toggle-border: $accordionmenu-border; +$accordionmenu-submenu-toggle-width: 40px; +$accordionmenu-submenu-toggle-height: $accordionmenu-submenu-toggle-width; +$accordionmenu-arrow-size: 6px; + +// 9. Badge +// -------- + +$badge-background: $primary-color; +$badge-color: $white; +$badge-color-alt: $black; +$badge-palette: $foundation-palette; +$badge-padding: 0.3em; +$badge-minwidth: 2.1em; +$badge-font-size: 0.6rem; + +// 10. Breadcrumbs +// --------------- + +$breadcrumbs-margin: 0 0 $global-margin 0; +$breadcrumbs-item-font-size: rem-calc(11); +$breadcrumbs-item-color: $primary-color; +$breadcrumbs-item-color-current: $black; +$breadcrumbs-item-color-disabled: $medium-gray; +$breadcrumbs-item-margin: 0.75rem; +$breadcrumbs-item-uppercase: true; +$breadcrumbs-item-separator: true; +$breadcrumbs-item-separator-item: '/'; +$breadcrumbs-item-separator-item-rtl: '\\'; +$breadcrumbs-item-separator-color: $medium-gray; + +// 11. Button +// ---------- + +$button-font-family: inherit; +$button-padding: 0.85em 1em; +$button-margin: 0 0 $global-margin 0; +$button-fill: solid; +$button-background: $primary-color; +$button-background-hover: scale-color($button-background, $lightness: -15%); +$button-color: $white; +$button-color-alt: $black; +$button-radius: $global-radius; +$button-hollow-border-width: 1px; +$button-sizes: ( + tiny: 0.6rem, + small: 0.75rem, + default: 0.9rem, + large: 1.25rem, +); +$button-palette: $foundation-palette; +$button-opacity-disabled: 0.25; +$button-background-hover-lightness: -20%; +$button-hollow-hover-lightness: -50%; +$button-transition: background-color 0.25s ease-out, color 0.25s ease-out; + +// 12. Button Group +// ---------------- + +$buttongroup-margin: 1rem; +$buttongroup-spacing: 1px; +$buttongroup-child-selector: '.button'; +$buttongroup-expand-max: 6; +$buttongroup-radius-on-each: true; + +// 13. Callout +// ----------- + +$callout-background: $white; +$callout-background-fade: 85%; +$callout-border: 1px solid rgba($black, 0.25); +$callout-margin: 0 0 1rem 0; +$callout-padding: 1rem; +$callout-font-color: $body-font-color; +$callout-font-color-alt: $body-background; +$callout-radius: $global-radius; +$callout-link-tint: 30%; + +// 14. Card +// -------- + +$card-background: $white; +$card-font-color: $body-font-color; +$card-divider-background: $light-gray; +$card-border: 1px solid $light-gray; +$card-shadow: none; +$card-border-radius: $global-radius; +$card-padding: $global-padding; +$card-margin-bottom: $global-margin; + +// 15. Close Button +// ---------------- + +$closebutton-position: right top; +$closebutton-offset-horizontal: ( + small: 0.66rem, + medium: 1rem, +); +$closebutton-offset-vertical: ( + small: 0.33em, + medium: 0.5rem, +); +$closebutton-size: ( + small: 1.5em, + medium: 2em, +); +$closebutton-lineheight: 1; +$closebutton-color: $dark-gray; +$closebutton-color-hover: $black; + +// 16. Drilldown +// ------------- + +$drilldown-transition: transform 0.15s linear; +$drilldown-arrows: true; +$drilldown-padding: $global-menu-padding; +$drilldown-nested-margin: 0; +$drilldown-background: $white; +$drilldown-submenu-padding: $drilldown-padding; +$drilldown-submenu-background: $white; +$drilldown-arrow-color: $primary-color; +$drilldown-arrow-size: 6px; + +// 17. Dropdown +// ------------ + +$dropdown-padding: 1rem; +$dropdown-background: $body-background; +$dropdown-border: 1px solid $medium-gray; +$dropdown-font-size: 1rem; +$dropdown-width: 300px; +$dropdown-radius: $global-radius; +$dropdown-sizes: ( + tiny: 100px, + small: 200px, + large: 400px, +); + +// 18. Dropdown Menu +// ----------------- + +$dropdownmenu-arrows: true; +$dropdownmenu-arrow-color: $anchor-color; +$dropdownmenu-arrow-size: 6px; +$dropdownmenu-arrow-padding: 1.5rem; +$dropdownmenu-min-width: 200px; +$dropdownmenu-background: $white; +$dropdownmenu-submenu-background: $dropdownmenu-background; +$dropdownmenu-padding: $global-menu-padding; +$dropdownmenu-nested-margin: 0; +$dropdownmenu-submenu-padding: $dropdownmenu-padding; +$dropdownmenu-border: 1px solid $medium-gray; +$dropdown-menu-item-color-active: get-color(primary); +$dropdown-menu-item-background-active: transparent; + +// 19. Flexbox Utilities +// --------------------- + +$flex-source-ordering-count: 6; +$flexbox-responsive-breakpoints: true; + +// 20. Forms +// --------- + +$fieldset-border: 1px solid $medium-gray; +$fieldset-padding: rem-calc(20); +$fieldset-margin: rem-calc(18 0); +$legend-padding: rem-calc(0 3); +$form-spacing: rem-calc(16); +$helptext-color: $black; +$helptext-font-size: rem-calc(13); +$helptext-font-style: italic; +$input-prefix-color: $black; +$input-prefix-background: $light-gray; +$input-prefix-border: 1px solid $medium-gray; +$input-prefix-padding: 1rem; +$form-label-color: $black; +$form-label-font-size: rem-calc(14); +$form-label-font-weight: $global-weight-normal; +$form-label-line-height: 1.8; +$select-background: $white; +$select-triangle-color: $dark-gray; +$select-radius: $global-radius; +$input-color: $black; +$input-placeholder-color: $medium-gray; +$input-font-family: inherit; +$input-font-size: rem-calc(16); +$input-font-weight: $global-weight-normal; +$input-line-height: $global-lineheight; +$input-background: $white; +$input-background-focus: $white; +$input-background-disabled: $light-gray; +$input-border: 1px solid $medium-gray; +$input-border-focus: 1px solid $dark-gray; +$input-padding: $form-spacing / 2; +$input-shadow: inset 0 1px 2px rgba($black, 0.1); +$input-shadow-focus: 0 0 5px $medium-gray; +$input-cursor-disabled: not-allowed; +$input-transition: box-shadow 0.5s, border-color 0.25s ease-in-out; +$input-number-spinners: true; +$input-radius: $global-radius; +$form-button-radius: $global-radius; + +// 21. Label +// --------- + +$label-background: $primary-color; +$label-color: $white; +$label-color-alt: $black; +$label-palette: $foundation-palette; +$label-font-size: 0.8rem; +$label-padding: 0.33333rem 0.5rem; +$label-radius: $global-radius; + +// 22. Media Object +// ---------------- + +$mediaobject-margin-bottom: $global-margin; +$mediaobject-section-padding: $global-padding; +$mediaobject-image-width-stacked: 100%; + +// 23. Menu +// -------- + +$menu-margin: 0; +$menu-nested-margin: $global-menu-nested-margin; +$menu-items-padding: $global-menu-padding; +$menu-simple-margin: 1rem; +$menu-item-color-active: $white; +$menu-item-background-active: get-color(primary); +$menu-icon-spacing: 0.25rem; +$menu-item-background-hover: $light-gray; +$menu-state-back-compat: true; +$menu-centered-back-compat: true; + +// 24. Meter +// --------- + +$meter-height: 1rem; +$meter-radius: $global-radius; +$meter-background: $medium-gray; +$meter-fill-good: $success-color; +$meter-fill-medium: $warning-color; +$meter-fill-bad: $alert-color; + +// 25. Off-canvas +// -------------- + +$offcanvas-size: 250px; +$offcanvas-vertical-size: 250px; +$offcanvas-background: $light-gray; +$offcanvas-shadow: 0 0 10px rgba($black, 0.7); +$offcanvas-inner-shadow-size: 20px; +$offcanvas-inner-shadow-color: rgba($black, 0.25); +$offcanvas-overlay-zindex: 11; +$offcanvas-push-zindex: 12; +$offcanvas-overlap-zindex: 13; +$offcanvas-reveal-zindex: 12; +$offcanvas-transition-length: 0.5s; +$offcanvas-transition-timing: ease; +$offcanvas-fixed-reveal: true; +$offcanvas-exit-background: rgba($white, 0.25); +$maincontent-class: 'off-canvas-content'; + +// 26. Orbit +// --------- + +$orbit-bullet-background: $medium-gray; +$orbit-bullet-background-active: $dark-gray; +$orbit-bullet-diameter: 1.2rem; +$orbit-bullet-margin: 0.1rem; +$orbit-bullet-margin-top: 0.8rem; +$orbit-bullet-margin-bottom: 0.8rem; +$orbit-caption-background: rgba($black, 0.5); +$orbit-caption-padding: 1rem; +$orbit-control-background-hover: rgba($black, 0.5); +$orbit-control-padding: 1rem; +$orbit-control-zindex: 10; + +// 27. Pagination +// -------------- + +$pagination-font-size: rem-calc(14); +$pagination-margin-bottom: $global-margin; +$pagination-item-color: $black; +$pagination-item-padding: rem-calc(3 10); +$pagination-item-spacing: rem-calc(1); +$pagination-radius: $global-radius; +$pagination-item-background-hover: $light-gray; +$pagination-item-background-current: $primary-color; +$pagination-item-color-current: $white; +$pagination-item-color-disabled: $medium-gray; +$pagination-ellipsis-color: $black; +$pagination-mobile-items: false; +$pagination-mobile-current-item: false; +$pagination-arrows: true; + +// 28. Progress Bar +// ---------------- + +$progress-height: 1rem; +$progress-background: $medium-gray; +$progress-margin-bottom: $global-margin; +$progress-meter-background: $primary-color; +$progress-radius: $global-radius; + +// 29. Prototype Arrow +// ------------------- + +$prototype-arrow-directions: ( + down, + up, + right, + left +); +$prototype-arrow-size: 0.4375rem; +$prototype-arrow-color: $black; + +// 30. Prototype Border-Box +// ------------------------ + +$prototype-border-box-breakpoints: $global-prototype-breakpoints; + +// 31. Prototype Border-None +// ------------------------- + +$prototype-border-none-breakpoints: $global-prototype-breakpoints; + +// 32. Prototype Bordered +// ---------------------- + +$prototype-bordered-breakpoints: $global-prototype-breakpoints; +$prototype-border-width: rem-calc(1); +$prototype-border-type: solid; +$prototype-border-color: $medium-gray; + +// 33. Prototype Display +// --------------------- + +$prototype-display-breakpoints: $global-prototype-breakpoints; +$prototype-display: ( + inline, + inline-block, + block, + table, + table-cell +); + +// 34. Prototype Font-Styling +// -------------------------- + +$prototype-font-breakpoints: $global-prototype-breakpoints; +$prototype-wide-letter-spacing: rem-calc(4); +$prototype-font-normal: $global-weight-normal; +$prototype-font-bold: $global-weight-bold; + +// 35. Prototype List-Style-Type +// ----------------------------- + +$prototype-list-breakpoints: $global-prototype-breakpoints; +$prototype-style-type-unordered: ( + disc, + circle, + square +); +$prototype-style-type-ordered: ( + decimal, + lower-alpha, + lower-latin, + lower-roman, + upper-alpha, + upper-latin, + upper-roman +); + +// 36. Prototype Overflow +// ---------------------- + +$prototype-overflow-breakpoints: $global-prototype-breakpoints; +$prototype-overflow: ( + visible, + hidden, + scroll +); + +// 37. Prototype Position +// ---------------------- + +$prototype-position-breakpoints: $global-prototype-breakpoints; +$prototype-position: ( + static, + relative, + absolute, + fixed +); +$prototype-position-z-index: 975; + +// 38. Prototype Rounded +// --------------------- + +$prototype-rounded-breakpoints: $global-prototype-breakpoints; +$prototype-border-radius: rem-calc(3); + +// 39. Prototype Separator +// ----------------------- + +$prototype-separator-breakpoints: $global-prototype-breakpoints; +$prototype-separator-align: center; +$prototype-separator-height: rem-calc(2); +$prototype-separator-width: 3rem; +$prototype-separator-background: $primary-color; +$prototype-separator-margin-top: $global-margin; + +// 40. Prototype Shadow +// -------------------- + +$prototype-shadow-breakpoints: $global-prototype-breakpoints; +$prototype-box-shadow: 0 2px 5px 0 rgba(0,0,0,.16), + 0 2px 10px 0 rgba(0,0,0,.12); + +// 41. Prototype Sizing +// -------------------- + +$prototype-sizing-breakpoints: $global-prototype-breakpoints; +$prototype-sizing: ( + width, + height +); +$prototype-sizes: ( + 25: 25%, + 50: 50%, + 75: 75%, + 100: 100% +); + +// 42. Prototype Spacing +// --------------------- + +$prototype-spacing-breakpoints: $global-prototype-breakpoints; +$prototype-spacers-count: 3; + +// 43. Prototype Text-Decoration +// ----------------------------- + +$prototype-decoration-breakpoints: $global-prototype-breakpoints; +$prototype-text-decoration: ( + overline, + underline, + line-through, +); + +// 44. Prototype Text-Transformation +// --------------------------------- + +$prototype-transformation-breakpoints: $global-prototype-breakpoints; +$prototype-text-transformation: ( + lowercase, + uppercase, + capitalize +); + +// 45. Prototype Text-Utilities +// ---------------------------- + +$prototype-utilities-breakpoints: $global-prototype-breakpoints; +$prototype-text-overflow: ellipsis; + +// 46. Responsive Embed +// -------------------- + +$responsive-embed-margin-bottom: rem-calc(16); +$responsive-embed-ratios: ( + default: 4 by 3, + widescreen: 16 by 9, +); + +// 47. Reveal +// ---------- + +$reveal-background: $white; +$reveal-width: 600px; +$reveal-max-width: $global-width; +$reveal-padding: $global-padding; +$reveal-border: 1px solid $medium-gray; +$reveal-radius: $global-radius; +$reveal-zindex: 1005; +$reveal-overlay-background: rgba($black, 0.45); + +// 48. Slider +// ---------- + +$slider-width-vertical: 0.5rem; +$slider-transition: all 0.2s ease-in-out; +$slider-height: 0.5rem; +$slider-background: $light-gray; +$slider-fill-background: $medium-gray; +$slider-handle-height: 1.4rem; +$slider-handle-width: 1.4rem; +$slider-handle-background: $primary-color; +$slider-opacity-disabled: 0.25; +$slider-radius: $global-radius; + +// 49. Switch +// ---------- + +$switch-background: $medium-gray; +$switch-background-active: $primary-color; +$switch-height: 2rem; +$switch-height-tiny: 1.5rem; +$switch-height-small: 1.75rem; +$switch-height-large: 2.5rem; +$switch-radius: $global-radius; +$switch-margin: $global-margin; +$switch-paddle-background: $white; +$switch-paddle-offset: 0.25rem; +$switch-paddle-radius: $global-radius; +$switch-paddle-transition: all 0.25s ease-out; + +// 50. Table +// --------- + +$table-background: $white; +$table-color-scale: 5%; +$table-border: 1px solid smart-scale($table-background, $table-color-scale); +$table-padding: rem-calc(8 10 10); +$table-hover-scale: 2%; +$table-row-hover: darken($table-background, $table-hover-scale); +$table-row-stripe-hover: darken($table-background, $table-color-scale + $table-hover-scale); +$table-is-striped: true; +$table-striped-background: smart-scale($table-background, $table-color-scale); +$table-stripe: even; +$table-head-background: smart-scale($table-background, $table-color-scale / 2); +$table-head-row-hover: darken($table-head-background, $table-hover-scale); +$table-foot-background: smart-scale($table-background, $table-color-scale); +$table-foot-row-hover: darken($table-foot-background, $table-hover-scale); +$table-head-font-color: $body-font-color; +$table-foot-font-color: $body-font-color; +$show-header-for-stacked: false; +$table-stack-breakpoint: medium; + +// 51. Tabs +// -------- + +$tab-margin: 0; +$tab-background: $white; +$tab-color: $primary-color; +$tab-background-active: $light-gray; +$tab-active-color: $primary-color; +$tab-item-font-size: rem-calc(12); +$tab-item-background-hover: $white; +$tab-item-padding: 1.25rem 1.5rem; +$tab-expand-max: 6; +$tab-content-background: $white; +$tab-content-border: $light-gray; +$tab-content-color: $body-font-color; +$tab-content-padding: 1rem; + +// 52. Thumbnail +// ------------- + +$thumbnail-border: solid 4px $white; +$thumbnail-margin-bottom: $global-margin; +$thumbnail-shadow: 0 0 0 1px rgba($black, 0.2); +$thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5); +$thumbnail-transition: box-shadow 200ms ease-out; +$thumbnail-radius: $global-radius; + +// 53. Title Bar +// ------------- + +$titlebar-background: $black; +$titlebar-color: $white; +$titlebar-padding: 0.5rem; +$titlebar-text-font-weight: bold; +$titlebar-icon-color: $white; +$titlebar-icon-color-hover: $medium-gray; +$titlebar-icon-spacing: 0.25rem; + +// 54. Tooltip +// ----------- + +$has-tip-cursor: help; +$has-tip-font-weight: $global-weight-bold; +$has-tip-border-bottom: dotted 1px $dark-gray; +$tooltip-background-color: $black; +$tooltip-color: $white; +$tooltip-padding: 0.75rem; +$tooltip-max-width: 10rem; +$tooltip-font-size: $small-font-size; +$tooltip-pip-width: 0.75rem; +$tooltip-pip-height: $tooltip-pip-width * 0.866; +$tooltip-radius: $global-radius; + +// 55. Top Bar +// ----------- + +$topbar-padding: 0.5rem; +$topbar-background: $light-gray; +$topbar-submenu-background: $topbar-background; +$topbar-title-spacing: 0.5rem 1rem 0.5rem 0; +$topbar-input-width: 200px; +$topbar-unstack-breakpoint: medium; + +// 56. Xy Grid +// ----------- + +$xy-grid: true; +$grid-container: $global-width; +$grid-columns: 12; +$grid-margin-gutters: ( + small: 20px, + medium: 30px +); +$grid-padding-gutters: $grid-margin-gutters; +$grid-container-padding: $grid-padding-gutters; +$grid-container-max: $global-width; +$block-grid-max: 8; diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css new file mode 100644 index 0000000000..74aaa3fd32 --- /dev/null +++ b/app/assets/stylesheets/application.css @@ -0,0 +1,179 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + *= require foundation_and_overrides + + */ + +/* ------ import URLS ------*/ +@import url('https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css'); +@import url('https://fonts.googleapis.com/css?family=Astloch:700|Josefin+Sans:400,600'); + + +* { + /*border: 1px solid lawngreen;*/ +} + + +/* ------ UNIVERSAL STYLES ------ */ +html { box-sizing: border-box; } +*, *:before, *:after { box-sizing: inherit; } +html, body { height: 100%; } +body { + width: 100%; + font-family: 'Josefin Sans', sans-serif; + font-size: 16px; + display: flex; + flex-direction: column; + background: #fafafa; +} + +.readable { + font-family: 'Josefin Sans', sans-serif; + font-style: normal; +} + +h1, h2, h3, h4, h5, h6 { + color: #16262E; + font-family: 'Astloch', cursive; + font-weight: 700; +} + +.page-holder { + margin: auto; + width: 95%; +} + +.align-center { + text-align: center; +} + +/*--------------- header ---------------*/ +header.header { + background: #3C7A89; + padding: 1% 0; +} +h1.main-heading { + margin: 0 0 0 1%; + white-space: nowrap; +} +h1.main-heading a { color: #fefefe; } +main.main { + padding: 0; + flex: 1 0 auto; +} +footer.footer { + color: #fcfcfc; + background: #16262E; + text-align: center; + padding: 3% 6% 2%; +} + +/*--------------- navigation ---------------*/ +header.header section { + display: flex; + flex-wrap: nowrap; + justify-content: space-between; + align-items: center; +} +nav.top-nav ul { + width: 100%; + display: flex; + flex-wrap: nowrap; + justify-content: flex-end; + align-items: center; + margin: 0; +} +nav.top-nav ul li { + width: 15%; + text-align: center; +} +nav.top-nav ul li a { color: #fefefe; } +.nav-text { + display: none; + white-space: nowrap; +} +.nav-icon { + font-size: 1.5rem; +} +@media print, screen and (min-width: 42em) { + .nav-text { + display: inline-block; + } + .nav-icon { + font-size: 1rem; + } +} + +.row { max-width: 100%; } +header nav { text-align: right; } +.right { float: right; } + +/*--------------- main ---------------*/ +main.main section:first-of-type { + padding: 1.5% 5% 6%; +} + +main.main section.hero { padding: 0; } + +.no-pad { + padding: 0 !important; +} + +.some-pad { + padding: 0 1rem !important; +} + +/*--------------- mini thumbnails ---------------*/ +div.mini-img { + width: 150px; + height: 150px; + margin: 1% 4% 2%; +} +div.mini-img a { + display: block; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} +div.mini-img a img { + max-width: 100%; + max-height: 100%; + filter: grayscale(100%); +} +div.mini-img:hover a img { filter: grayscale(0%); } + +/*------------------ errors ------------------*/ + +.errors { + border: 2px solid #A7AAB9; + border-radius: 1rem; + float: none; + margin: 1rem auto; + padding: 1rem 4rem; +} + +.errors li { + line-height: 1; +} + +.failure { + color: #7F4F62; +} + +.success { + color: #91BEC9; +} diff --git a/app/assets/stylesheets/categories.scss b/app/assets/stylesheets/categories.scss new file mode 100644 index 0000000000..169f888a85 --- /dev/null +++ b/app/assets/stylesheets/categories.scss @@ -0,0 +1,71 @@ +// Place all the styles related to the categories controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ + +// Palette: +$pink: #DBC2CF; +$grey: #A7AAB9; +$gray: #A7AAB9; +$teal: #3C7A89; +$ltteal: #91BEC9; +$navy: #16262E; +$burgundy: #4F3640; +$ltburgundy: #7F4F62; + +//////////////////// +// ADD CATEGORIES // +//////////////////// + +.add-cats-holder { + padding-top: 3rem; +} + +.check-with-label:checked + .label-for-check { + font-weight: bold; +} + +.check-with-label:checked + .form-item { + background-color: $pink; +} + +.check-with-label:checked + .form-item:hover { + background-color: $ltteal; +} + +input[type="checkbox"] { + display: none; +} + +.add-category { + display: inline-block !important; +} + +.add-category[type="text"], .add-category[type="submit"] { + width: 45%; + margin: 0; +} + +.center { + text-align: center; +} + +.cat-form { + border: 1px solid grey; + padding: 1rem; +} + +.cat-btn { + margin-top: 1rem !important; +} + +label.form-item { + background-color: #eee; + border: 1px solid $grey; + display: block !important; + margin-top: -1px; +} + + +.form-item:hover { + background-color: $ltteal; +} diff --git a/app/assets/stylesheets/foundation_and_overrides.scss b/app/assets/stylesheets/foundation_and_overrides.scss new file mode 100644 index 0000000000..7b55083cfb --- /dev/null +++ b/app/assets/stylesheets/foundation_and_overrides.scss @@ -0,0 +1,57 @@ +@charset 'utf-8'; + +@import 'settings'; +@import 'foundation'; + +// If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package. +// +// @import 'motion-ui/motion-ui'; + +// We include everything by default. To slim your CSS, remove components you don't use. + +@include foundation-global-styles; +@include foundation-grid; +@include foundation-typography; +@include foundation-button; +@include foundation-forms; +@include foundation-visibility-classes; +@include foundation-float-classes; +@include foundation-accordion; +@include foundation-accordion-menu; +@include foundation-badge; +@include foundation-breadcrumbs; +@include foundation-button-group; +@include foundation-callout; +@include foundation-card; +@include foundation-close-button; +@include foundation-drilldown-menu; +@include foundation-dropdown; +@include foundation-dropdown-menu; +@include foundation-responsive-embed; +@include foundation-label; +@include foundation-media-object; +@include foundation-menu; +@include foundation-menu-icon; +@include foundation-off-canvas; +@include foundation-orbit; +@include foundation-pagination; +@include foundation-progress-bar; +@include foundation-slider; +@include foundation-sticky; +@include foundation-reveal; +@include foundation-switch; +@include foundation-table; +@include foundation-tabs; +@include foundation-thumbnail; +@include foundation-title-bar; +@include foundation-tooltip; +@include foundation-top-bar; + +// If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package. +// +// @include motion-ui-transitions; +// @include motion-ui-animations; + @import 'motion-ui/motion-ui'; + @include motion-ui-transitions; + @include motion-ui-animations; + diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss new file mode 100644 index 0000000000..e7bf587939 --- /dev/null +++ b/app/assets/stylesheets/main.scss @@ -0,0 +1,96 @@ +// Place all the styles related to the Main controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ +// @keyframes fadeIn { +// 0% {opacity: 0;} +// 100% {opacity: 1;} +// } +@-webkit-keyframes fadeIn { + 0% {opacity: 0;} + 100% {opacity: 1;} +} + +@keyframes fadeIn { + 0% {opacity: 0;} + 100% {opacity: 1;} +} + +.hero { + background-image: url('http://3.bp.blogspot.com/-Z23LsR1pXhE/VN7uK5CR9yI/AAAAAAAAAOk/N_CksAB4Cbw/s1600/Dark%2BMagic%2BFloating%2BAway%2BFrom%2BCreepy%2BForest.jpg'); + background-position: center center; + background-size: cover; + background-repeat: no-repeat; + height: 45rem; + text-align: center; + width: 100%; +} +.hero-text { + font-family: 'Josefin Sans', sans-serif; + display: inline-block; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.5); +} + +.hero-text h1, .hero-text h2 { + color: #fefefe; +} + +h1 span.hero-lorestore { + font-family: 'Astloch', cursive; + font-size: 3em; + -webkit-animation-duration: 5s; + animation-duration: 5s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + -webkit-animation-name: fadeIn; + animation-name: fadeIn; +} +h1.hero-title, h2.hero-subtitle { font-family: 'Josefin Sans', sans-serif; } +h1.hero-title { + font-size: 2.5em; + margin-top: 10%; +} +h2.hero-subtitle { + font-size: 1.8em; + margin-top: 5%; +} + +////////// +// CART // +////////// + + +.shopping-cart-info > div { + margin: 2rem 0; + overflow-x: hidden; + white-space: nowrap; +} + +.cart-text { + line-height: 2; +} + +label.shopping-cart-form { + display: inline; + text-align: right; + width: 40%; +} + +input.shopping-cart-form { + display: inline-block; + width: 50%; +} + +.view-cart { + float: none; + margin: auto; +} + +.cart-info .button:first-child { + border-right: 1px solid #efefef; +} + +.view-cart .product-img.product-thumbnail { + margin: .5rem 0 .5rem 1rem; +} diff --git a/app/assets/stylesheets/merchants.scss b/app/assets/stylesheets/merchants.scss new file mode 100644 index 0000000000..1a976580ae --- /dev/null +++ b/app/assets/stylesheets/merchants.scss @@ -0,0 +1,179 @@ +// Place all the styles related to the merchants controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ + +// Palette: +$pink: #DBC2CF; +$grey: #A7AAB9; +$gray: #A7AAB9; +$teal: #3C7A89; +$navy: #16262E; +$burgundy: #4F3640; + + +///////////// +// PROFILE // +///////////// + +.profile { + float: none !important; + margin: auto; +} + +.profile-card { + // background-color: $teal; + border: 2px solid $teal; + border-radius: 2rem; + // box-shadow: 3px 2px 0 $navy; + color: $navy; + text-align: center; + padding: 2rem 0; + margin: 1rem auto; + min-width: 15.5rem; +} + +.profile-huge { + font-size: 5rem; +} + +.card-title { + height: 4rem; +} + +.profile-button { + border-radius: 2rem; + display: block; + margin: auto; + min-width: 12rem; + width: 80%; +} + +.profile-button:hover { + background-color: $pink; +} + +.summary-empty { + color: grey; +} + +.summary-urgent { + color: lightcoral; +} + + +// @media print, screen and (min-width: 40em) { +// .profile-card { +// min-height: 15.5rem; +// } +// } + + +///////////// +// REVENUE // +///////////// + +table.revenue { + margin: auto; + float: none !important; +} + +.revenue-total { + color: $teal; +} + +/////////////// +// INVENTORY // +/////////////// + +.inventory-tracker { + border: 1px solid $grey; + margin-left: auto !important; + margin-right: auto !important; + margin-top: -1px; + padding: 0; +} + +.inventory-info > div { + // margin: 2rem 0; + overflow-x: hidden; + white-space: nowrap; +} + +label.inventory-form { + display: inline-block; + margin-top: .35rem; + padding-left: 0; + width: 30%; +} + +input.inventory-form { + display: inline-block; + float: right; +} + +.new-product-button { + margin: 3rem 0; +} + +.product-list h4 small { + color: $teal; +} + +@media print, screen and (min-width: 40em) { + input.inventory-form { + width: 70%; + } +} + +@media print, screen and (min-width: 64em) { + input.inventory-form { + width: 65%; + } + label.inventory-form { + text-align: right; + } + .inventory-info > div { + margin: 2rem 0; + } +} + +.inv-item { + padding: .25rem; +} + +.spacer { + margin-top: 5rem; +} + +///////////////// +// ORDER PAGES // +///////////////// + +.order-tracker { + border: 1px solid $grey; + margin-left: auto !important; + margin-right: auto !important; + margin-top: -1px; + padding: 1rem 0; +} + +.order-tracker span { + margin: 0; +} + +.ord-name { + overflow-x: hidden; + white-space: nowrap; +} + +.button.ship-btn { + margin: 0; +} + +.burg-btn { + background-color: $burgundy; +} + +.burg-btn:hover { + background-color: $pink; +} diff --git a/app/assets/stylesheets/order_products.scss b/app/assets/stylesheets/order_products.scss new file mode 100644 index 0000000000..a4e6e65327 --- /dev/null +++ b/app/assets/stylesheets/order_products.scss @@ -0,0 +1,32 @@ +// Place all the styles related to the order_products controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ + +.product-img.product-thumbnail { + display: inline-block; + height: 7rem; + margin: .5rem 1rem; + width: 7rem; +} + +.info { + display: inline-block; +} + +.info > .row > .row { + display: inline-block; +} + +.order-product h4, .order-product p { + display: inline-block; +} + +.order-product.row { + border: 1px solid grey; + margin: 1rem auto; + width: 100%; +} + +.ship-btn { + margin-top: 1rem; +} diff --git a/app/assets/stylesheets/orders.scss b/app/assets/stylesheets/orders.scss new file mode 100644 index 0000000000..101a7f6023 --- /dev/null +++ b/app/assets/stylesheets/orders.scss @@ -0,0 +1,93 @@ +// Place all the styles related to the orders controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ + +//------------- ICON IMGS ---------------- +.tiny-img { + margin-left: 0 1% 0 2%; + width: 48px; + height: 48px; +} + +//------------- ORDER #INDEX ---------------- +h3.no-orders { + font-family: 'Josefin Sans', sans-serif; + margin: 1% 0; +} +h2.page-title { font-size: 2.5em; } +section.order-index article { + width: 100%; + margin-top: 2%; + padding: 2% 2% 1%; + border: 2px solid #eee; + border-radius: 8px; + background: #fefefe; + display: flex; + justify-content: space-around; + align-items: flex-start; +} +div.o-details { width: 25%; } +p.o-title { + color: #999; + font-size: 0.8em; + line-height: 0.5; +} +div.o-products { + width: 75%; + display: flex; + flex-direction: column; +} +div.oitem-wrapper { + width: 100%; + display: flex; + flex-wrap: none; + justify-content: flex-start; + align-items: center; +} +div.olist-wrapper { width: 70%; } +h4.oitem-name { + font-family: 'Josefin Sans', sans-serif; + font-weight: 600; + font-style: normal; + color: #4F3640; +} +span.oitem-qty { margin-left: 10%; } +p.od-btn a.ls-btn { + display: block; + width: 100%; + height: 100%; + text-align: center; + padding: 4% 2%; + background: #4F3640; + color: #fefefe; +} + +//------------- ORDER #SHOW ---------------- +div.odetails-top { + width: 95%; + margin: 2% auto 3%; + display: flex; + align-items: flex-start; +} +div.odetails-heading { width: 40%; } +div.odetails-summary { width: 40%; } +span.fa-stack { + margin-right: 2%; +} +div.odetails-products table thead tr th, +div.odetails-products table tbody tr td { + text-align: center; +} +div.odetails-products table tbody tr td:nth-of-type(2) { + display: flex; + justify-content: space-around; + align-items: center; +} + +//------------- ORDER #CONFIRM ---------------- +div.oconfirm-wrapper { margin: 2% 0; } +div.oconfirm-summary ul.oconfirm-list { list-style-type: none; } +span.bold-data { + font-weight: 600; + width: 30%; +} diff --git a/app/assets/stylesheets/products.scss b/app/assets/stylesheets/products.scss new file mode 100644 index 0000000000..15cdeff3f2 --- /dev/null +++ b/app/assets/stylesheets/products.scss @@ -0,0 +1,234 @@ +// Place all the styles related to the products controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ + + +// Palette: +$pink: #DBC2CF; +$grey: #A7AAB9; +$gray: #A7AAB9; +$teal: #3C7A89; +$ltteal: #91BEC9; +$navy: #16262E; +$burgundy: #4F3640; +$ltburgundy: #7F4F62; + + +//////////////////// +// PRODUCTS LIST: // +//////////////////// + +nav ul { + list-style: none; + margin-left: 0; +} + +.product { + height: 17rem; + margin: .25rem; + padding: 0; + padding-top: 1.25rem; + text-align: center; + width: 16rem; +} + +.product:hover { + color: #3C7A89; +} + +.product a { + color: inherit; +} + +.product:last-of-type { + float: left !important; +} + +.product p { + margin-bottom: 0; +} + +.product-list .product-img { + height: 15rem; + margin: auto auto .25rem auto; + width: 15rem; +} + +.product-img { + background-image: url(http://via.placeholder.com/320x320); + background-position: center center; + background-repeat: no-repeat; + background-size: cover; + margin: auto; + // margin: 0; + overflow: hidden; +} + + +@media print, screen and (min-width: 64em) { + .product { + height: 19rem; + margin: 0; + padding: 0; + padding-top: 1.25rem; + text-align: center; + width: 19rem; + } +} + +//////////////////// +// PRODUCTS PAGE: // +//////////////////// + +section.product-info { + margin: auto; + float: none; +} + +.product-index { + border-top: 1px solid $grey; +} + +.product-info .row { + margin: 1rem auto; +} + +.product-info .product-img { + height: 20rem; + width: 20rem; +} + +.product-details { + padding: 2rem; +} + +.product-details p, .product-details label { + font-size: 1.25rem; +} + +.product-details label { + display: inline; + padding-left: 0; +} + +.product-details select { + display: inline; + // width: 30%; +} + +.product-details .cart-btn { + display: block; + font-size: 1.5rem; + margin: auto; + padding: 1rem 2rem; +} + +.show-product { + height: 20rem; +} + +@media print, screen and (min-width: 40em) { + .product-index { + border-top: 0; + border-left: 1px solid $grey; + } +} + + +////////////////////// +// CATEGORIES LIST: // +////////////////////// + +.prod-cats { + list-style: none; +} + +.prod-cats ul { + margin-bottom: 0; + margin-left: 0; +} + +.prod-cats li { + background-color: white; + border: 2px solid #9FA2B2; + border-radius: 2rem; + color: #9FA2B2; +} + +.prod-cats a { + color: inherit; +} + +.prod-cats li:hover { + background-color: white; + border-color: #DBC2CF; + color: #DBC2CF; +} + +.prod-cats li:hover a { + color: inherit; +} + +.prod-cats .add-categories { + background-color: #DBC2CF; + color: white; +} + +.prod-cats .add-categories:hover { + background-color: #4F3640; + border-color: #9FA2B2; + color: white; +} + +////////////////////// +// CATEGORY SIDEBAR // +////////////////////// + +.category-menu { + padding-top: 1rem; + // min-width: 7.5rem; +} + +.category-menu li a { + color: $grey; + display: inline-block; + text-indent: -.5rem; +} + +.category-menu li a:hover { + color: $ltteal; +} + +.view-all { + font-weight: bold; + margin-bottom: 1rem; +} + +///////////////// +// NEW PRODUCT // +///////////////// + +.product-form { + float: none; + margin: auto; + padding: 1rem; +} + +.prod-form-item { + display: inline-block; +} + +@media print, screen and (min-width: 40em) { + input.prod-form-item, textarea.prod-form-item { + width: 70%; + } +} + +@media print, screen and (min-width: 64em) { + input.prod-form-item, textarea.prod-form-item { + width: 75%; + } + label.prod-form-item { + // text-align: right; + } +} diff --git a/app/assets/stylesheets/reviews.scss b/app/assets/stylesheets/reviews.scss new file mode 100644 index 0000000000..a0f4041661 --- /dev/null +++ b/app/assets/stylesheets/reviews.scss @@ -0,0 +1,140 @@ +// Place all the styles related to the reviews controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ + +///////***Partial view***//////////// + +.first_line_info h6 { + display: inline-block; +} + +.first_line_info { + color: #4F3640; +} + +.button.change_review { + background-color: #7F4F62; + border: .5px solid grey; + border-radius: 1rem; +} + +.button.change_review:hover{ + background-color: #DBC2CF; +} + +.button.change_review a { + color: white; +} + +.review_info { + padding-left: 10%; + border: .5px solid grey; + border-radius: 1rem; + border-color: #9FA2B2; + color: grey; + margin-bottom: 20px; +} + +.rating_review { + padding-left: 30%; +} +.button.add_review_button { + background-color: #4F3640; + margin-left: 30%; + border-radius: 0.5em; +} + +.button.add_review_button a, .button.submit_review a { + color: white; +} + +.button.cart-btn { + background-color: #4F3640; + border-radius: 0.5em; +} + +.button.cart-btn:hover,.button.add_review_button:hover,.button.submit_review:hover{ + background-color: #DBC2CF; +} + +.button.submit_review { + background-color: #4F3640; + border-radius: 0.5em; +} + +.stars { + color: gold; +} + +.review_date { +padding-left: 15%; +} +//////////////****Review form****///////////// + +.row.align_center { + text-align: center; + margin-top: 30px; + + .description { + margin-top: 20px; + margin-bottom: 40px; + max-width: 600px; + margin-left: 320px; + } +} + +.rating { + display: inline-block; + position: relative; + top: 8px; + left: -10px; +} + +.rating:not(:checked) > input { + position:absolute; + top:-9999px; + clip:rect(0,0,0,0); +} + +.rating:not(:checked) > label { + float:right; + width:1.2em; + padding:0 .1em; + overflow:hidden; + white-space:nowrap; + cursor:pointer; + font-size:200%; + line-height:1.2; + color:#ddd; + text-shadow:1px 1px #bbb, 2px 2px #666, .1em .1em .2em rgba(0,0,0,.5); +} + +.rating:not(:checked) > label:before { + content: '★ '; +} + +.rating > input:checked ~ label { + color: #f70; + text-shadow:1px 1px #c60, 2px 2px #940, .1em .1em .2em rgba(0,0,0,.5); +} + +.rating:not(:checked) > label:hover, +.rating:not(:checked) > label:hover ~ label { + color: gold; + text-shadow:1px 1px goldenrod, 2px 2px #B57340, .1em .1em .2em rgba(0,0,0,.5); +} + +.rating > input:checked + label:hover, +.rating > input:checked + label:hover ~ label, +.rating > input:checked ~ label:hover, +.rating > input:checked ~ label:hover ~ label, +.rating > label:hover ~ input:checked ~ label { + color: #ea0; + text-shadow:1px 1px goldenrod, 2px 2px #B57340, .1em .1em .2em rgba(0,0,0,.5); +} + +.rating > label:active { + position:relative; + top:2px; + left:2px; +} diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb new file mode 100644 index 0000000000..d672697283 --- /dev/null +++ b/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb new file mode 100644 index 0000000000..0ff5442f47 --- /dev/null +++ b/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 0000000000..9c00a86610 --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,37 @@ +class ApplicationController < ActionController::Base + protect_from_forgery with: :exception + + before_action :no_nil_cart + + protected + + def no_nil_cart + session[:cart] ||= [] + end + + def confirm_login + if session[:merchant].nil? + flash[:status] = :failure + flash[:message] = "You must be logged in to do that." + return redirect_back(fallback_location: root_path) + end + end + + def find_object_by_params(model) + @product = model.find_by(id: params[:id]) + unless @product + return head :not_found + end + return @product + end + + def confirm_object_ownership(model, merchant_id) + unless session[:merchant]['id'] == merchant_id + flash[:status] = :failure + flash[:message] = "Only a #{model}'s creator can modify a #{model}." + return redirect_back(fallback_location: root_path) + end + end + + +end diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb new file mode 100644 index 0000000000..d8ce5096ee --- /dev/null +++ b/app/controllers/categories_controller.rb @@ -0,0 +1,32 @@ +class CategoriesController < ApplicationController + before_action :confirm_login + + before_action only: [:add] do + find_object_by_params(Product) + end + + before_action only: [:add] do + confirm_object_ownership(@product, @product.merchant_id) + end + + + def create + category = Category.new(name: params[:category_name]) + result = category.save + + if result + flash[:status] = :success + flash[:message] = "Added category #{category.name}" + return redirect_to add_categories_path(params[:product_id]) + else + flash[:status] = :failure + flash[:message] = "Could not create new category" + flash[:details] = category.errors.messages + return redirect_to add_categories_path(params[:product_id]) + end + end + + def add + @categories = Category.all + end +end diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb new file mode 100644 index 0000000000..901b65aff3 --- /dev/null +++ b/app/controllers/main_controller.rb @@ -0,0 +1,27 @@ +class MainController < ApplicationController + + before_action only: [:add_to_cart] do + find_object_by_params(Product) + end + + def index + end + + def add_to_cart + order_product = OrderProduct.create(quantity: params["quantity"], product_id: @product.id, status: "pending") + if order_product.valid? + session[:cart] << order_product.id + flash[:status] = :success + flash[:message] = "#{@product.name} added to cart." + redirect_to products_path + else + flash[:status] = :failure + flash[:message] = "#{@product.name} could not be added to cart." + redirect_to products_path + end + end + + def shopping_cart + @products = OrderProduct.find_in_cart(session[:cart]) + end +end diff --git a/app/controllers/merchants_controller.rb b/app/controllers/merchants_controller.rb new file mode 100644 index 0000000000..11eff76d80 --- /dev/null +++ b/app/controllers/merchants_controller.rb @@ -0,0 +1,155 @@ +class MerchantsController < ApplicationController + + before_action :confirm_login, only: [:summary, :pending, :completed, :mark_shipped, :revenue, :inventory] + + before_action only: [:mark_shipped] do + @order_product = find_object_by_params(OrderProduct) + end + + before_action only: [:mark_shipped] do + confirm_object_ownership(@order_product, @order_product.product.merchant_id) + end + + + def login + auth_hash = request.env['omniauth.auth'] + merchant = Merchant.find_by(oauth_uid: auth_hash['uid'], oauth_provider: auth_hash['provider']) + + unless merchant + merchant = Merchant.by_auth_hash(auth_hash) + unless merchant.save + flash.now[:status] = :failure + flash.now[:result_text] = "Not logged in" + flash.now[:messages] = merchant.errors.messages + return redirect_to root_path + end + end + session[:merchant] = merchant + redirect_to root_path + end + + def logout + session[:merchant] = nil + redirect_to root_path + end + #CRUD + + # def new + # @merchant = Merchant.new + # end + + def create + @merchant = Merchant.new(merchant_params) + + if @merchant.save + #success message + flash[:status] = :success + flash[:message] = "Successfully created merchant #{@merchant.id}" + redirect_to root_path + else + #I'm not sure how we're deciding to do error messages. Flash? + flash.now[:status] = :failure + flash.now[:message] = "Failed to create Merchant" + flash.now[:details] = @merchant.errors.messages + render :new, status: :bad_request + end + end + + # def index + # @merchants = Merchant.all + # end + + def edit + @merchant = Merchant.find_by(id: params[:id]) + return head :not_found unless @merchant + end + + def update + if find_merchant_by_params_id + @merchant.update_attributes(merchant_params) + + if @merchant.save + flash[:status] = :success + flash[:message] = "Successfully updated #{@merchant.username}" + redirect_to merchant_path(@merchant) + return + + else + flash.now[:status] = :failure + flash.now[:message] = "Could not update #{@merchant.username}" + # flash.now[:messages] = @merchant.errors.messages + render :edit, status: :bad_request + # return + end + end + end + + def show + @merchant = Merchant.find_by(id: params[:id]) + return head :not_found unless @merchant + end + + def destroy + @merchant = Merchant.find_by(id: params[:id]) + @merchant.destroy + redirect_to root_path + end + + # USER PAGES + def summary + @user = Merchant.find(session[:merchant]['id']) + end + + def pending + @order_products = Merchant.find(session[:merchant]['id']).pending_orders + end + + def completed + @order_products = Merchant.find(session[:merchant]['id']).shipped_orders + end + + def mark_shipped + order_product = OrderProduct.find_by(id: params[:id]) + if order_product + order_product.status = "shipped" + order_product.save + order_product.order.order_status + flash[:status] = :success + flash[:message] = "Marked #{order_product.product.name} as shipped" + return redirect_to self_pending_path + else + return head :not_found + end + end + + def revenue + @user = Merchant.find(session[:merchant]['id']) + @all = @user.own_orders + @pending = @user.pending_orders + @completed = @user.shipped_orders + @all_average = (@all.count == 0 ? 0 : (@all.sum{|order| order.total})/@all.count) + @pending_average = (@pending.count == 0 ? 0 : (@pending.sum{|order| order.total})/@pending.count) + @completed_average = (@completed.count == 0 ? 0 : (@completed.sum{|order| order.total})/@completed.count) + end + + def inventory + merchant = Merchant.find(session[:merchant]['id']) + @products = merchant.active_products + @inactive_products = merchant.inactive_products + end + + private + + def merchant_params + return params.require(:merchant).permit(:username, :email, :oauth_uid, :oauth_provider) + end + + def find_merchant_by_params_id + @merchant = Merchant.find_by(id: params[:id]) + unless @merchant + head :not_found + end + return @merchant + end + +end diff --git a/app/controllers/order_products_controller.rb b/app/controllers/order_products_controller.rb new file mode 100644 index 0000000000..af996164e1 --- /dev/null +++ b/app/controllers/order_products_controller.rb @@ -0,0 +1,41 @@ +class OrderProductsController < ApplicationController + def index + @order_products = OrderProduct.all.find_all {|op| (op.product.merchant_id == params['merchant_id'].to_i) && op.order != nil}.sort_by{|op| -op.status} + end + + def update + if find_order_product_by_params_id + @order_product.update_attributes(order_product_params) + if @order_product.save && @order_product.check_quantity + redirect_to shopping_cart_path + return + else + render 'main/shopping_cart', status: :bad_request + return + end + end + end + + def destroy + if find_order_product_by_params_id + @order_product.destroy + flash[:status] = :success + flash[:message] = "Deleted item #{@order_product.product.name} from cart" + redirect_to shopping_cart_path + end + end + + private + + def order_product_params + return params.require(:order_product).permit(:quantity, :status, :product_id, :order_id) + end + + def find_order_product_by_params_id + @order_product = OrderProduct.find_by(id: params[:id]) + unless @order_product + head :not_found + end + return @order_product + end +end diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb new file mode 100644 index 0000000000..c6a4dbdfcf --- /dev/null +++ b/app/controllers/orders_controller.rb @@ -0,0 +1,113 @@ +class OrdersController < ApplicationController + + before_action :confirm_login, except: [:new, :create, :confirmation] + + def index + # show only the orders that belong to the merchant + if session[:merchant] + merchant_id = session[:merchant]['id'] + @orders = Merchant.find(merchant_id).orders + end + end + + def new + # new order, either with no merchant_id or with the logged-in-user's merchant_id + @order = Order.new + end + + def create + # create new order, either with no merchant_id or with the logged-in-user's merchant_id + @order = Order.new(order_params) + @order.order_products += OrderProduct.find_in_cart(session[:cart]) + + if @order.save && @order.order_products.length > 0 + @order.decrement_products + session[:cart] = nil + flash[:status] = :success + flash[:message] = "Successfully created order" + redirect_to confirmation_path(@order) + else + flash.now[:status] = :failure + flash.now[:message] = "Failed to create order" + flash.now[:details] = @order.errors.messages + render :new, status: :bad_request + end + end + + def show + # show the order if it belongs to the merchant (buyer) + find_order_by_params_id + end + + def edit + # edit the order if it belongs to the merchant (buyer) + find_order_by_params_id + confirm_object_ownership(@order, @order.merchant_id) + end + + def update + # edit the desired order if it belongs to the merchant (buyer) + if find_order_by_params_id + @order.update_attributes(order_params) + if @order.save + flash[:status] = :success + flash[:message] = "Successfully updated order" + redirect_to order_path(@order) + return + else + flash.now[:status] = :failure + flash.now[:message] = "Order could not be updated" + flash.now[:details] = @order.errors.messages + render :edit, status: :bad_request + return + end + end + end + + def confirmation + if session['merchant'] + find_order_by_params_id + confirm_object_ownership(@order, @order.merchant_id) + else + return redirect_to products_path + end + end + + def cancel + find_order_by_params_id + if @order + if @order.order_products.any? { + |op| op.status == "shipped" + } + flash[:status] = :failure + flash[:message] = "Cannot cancel an order if one of the products has been shipped" + render :show, status: :bad_request + else + @order.increment_products + @order.status = "cancelled" + @order.save + flash[:status] = :success + flash[:message] = "Successfully cancelled the order" + redirect_to order_path(@order) + end + else + return head :not_found + end + end + + private + + def order_params + # parameters for the order + return params.require(:order).permit(:cust_name, :status, :cust_email, :cust_cc, :cust_cc_exp, :cust_addr, :merchant_id, :order_products, :cvv, :zip_code) + end + + def find_order_by_params_id + # find the order by the id parameter + @order = Order.find_by(id: params[:id]) + unless @order + head :not_found + end + return @order + end +end diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb new file mode 100644 index 0000000000..e15549a1f0 --- /dev/null +++ b/app/controllers/products_controller.rb @@ -0,0 +1,119 @@ +class ProductsController < ApplicationController + + before_action only: [:show, :edit, :update, :destroy, :categories, :add_categories, :retire] do + find_object_by_params(Product) + end + + before_action :confirm_login, except: [:index, :show] + + before_action only: [:edit, :update, :destroy, :categories, :add_categories, :retire] do + confirm_object_ownership(@product, @product.merchant_id) + end + + + def index + if params[:category_id] + if params[:category_id] == 'all' + @products = Product.all + @title = "All Products" + else + @cat = Category.find_by(id: params[:category_id]) + if @cat + @products = Product.all.find_all { |prod| prod.categories.include? @cat } + @title = @cat.name.capitalize + else + return head :not_found + end + end + else + @title = "Popular Now" + @products = Product.most_popular + end + @products = @products.reject {|prod| prod.quantity == 0} + @categories = Category.all + end + + def show + if session[:merchant] + @own_product = session[:merchant]['id'] == @product.merchant_id ? true : false + end + end + + def new + @product = Product.new + end + + def create + @product = Product.new(prod_params) + @product.merchant_id = session[:merchant]['id'] + + result = @product.save + + if result + flash.now[:status] = :success + flash.now[:message] = "Successfully created #{@product.name}" + return redirect_to add_categories_path(@product.id) + else + flash.now[:status] = :failure + flash.now[:message] = "Could not create new product" + flash.now[:details] = @product.errors.messages + return render :new, status: :bad_request + end + end + + def edit + end + + def update + result = @product.update(prod_params) + + if result + flash.now[:status] = :success + flash.now[:message] = "Successfully updated #{@product.name}" + if request.referer.include?'inventory' + return redirect_to self_inventory_path + else + return redirect_to product_path(@product.id) + end + else + flash.now[:status] = :failure + flash.now[:message] = "Could not update product" + flash.now[:details] = @product.errors.messages + return render :edit, status: :bad_request + end + end + + def destroy + result = @product.destroy + + flash.now[:status] = :success + flash.now[:message] = "Successfully deleted #{@product.name}" + return redirect_to products_path + end + + def add_categories + result = @product.add_categories_by_params(params) + + if result + flash.now[:status] = :success + flash.now[:message] = "Added category" + else + flash.now[:status] = :failure + flash.now[:message] = "Could not add category" + flash.now[:details] = @product.errors.messages + end + return redirect_to product_path(@product.id) + end + + def retire + @product.quantity = 0 + @product.save + return redirect_to self_inventory_path + end + + private + + def prod_params + params.require(:product).permit(:name, :image_url, :price, :quantity, :description) + end +end diff --git a/app/controllers/reviews_controller.rb b/app/controllers/reviews_controller.rb new file mode 100644 index 0000000000..d1115a2e95 --- /dev/null +++ b/app/controllers/reviews_controller.rb @@ -0,0 +1,113 @@ +class ReviewsController < ApplicationController + before_action :find_review_by_params_id, only: [:edit, :update, :destroy] #:show, + before_action :check_for_product_owner_nested, only: [:new] + before_action :check_for_product_owner, only: [:edit, :update, :destroy] + before_action only: [:edit, :destroy] do + + confirm_object_ownership(@review, @review.merchant_id) + end + # leaving for future, if we rethink and decide to add later + # def index + # @reviews = Review.where(product_id: params[:product_id]) + # end + + # def show ; end + + def new + @review = Review.new + @review.product_id = params[:product_id] + end + + def create + @review = Review.new(review_params) + @review.merchant_id = session[:merchant]["id"] if session[:merchant] + if @review.save + flash[:status] = :success + flash[:message] = "Successfully created review " + redirect_to product_path(@review.product) + else + flash[:status] = :failure + flash[:message] = "Failed to create review" + render :new, status: :bad_request + end + end + + def edit ; end + + def update + @review.update_attributes(review_params) + result = @review.save + if result + flash[:status] = :success + flash[:message] = "Successfully created review " + redirect_to product_path(@review.product_id) + else + flash.now[:status] = :failure + flash.now[:message] = "Could not update the review" + flash.now[:details] = @review.errors.messages + return render :edit, status: :bad_request + end + end + + def destroy + @review.destroy + flash[:status] = :success + flash[:message] = "Successfully destroyed review" + redirect_to product_path(@review.product_id) + end + + private + + def review_params + return params.require(:review).permit(:rating, :description, :merchant_id, :product_id) + end + + def find_review_by_params_id + @review = Review.find_by(id: params[:id]) + unless @review + head :not_found + end + end + + def check_for_product_owner + if !session[:merchant].nil? && @review.product.merchant_id == session[:merchant]["id"] + flash[:status] = :failure + flash[:message] = "Owner can not edit the review of the product!" + redirect_to product_path(@review.product_id) + end + end + + def check_for_product_owner_nested + @product = Product.find_by(id: params[:product_id]) + unless @product + head :not_found + end + if !session[:merchant].nil? && @product.merchant_id == session[:merchant]["id"] + flash[:status] = :failure + flash[:message] = "Owner can not review the product!" + redirect_to product_path(@product) + end + end + + # def check_for_product_owner + # unless session[:merchant].nil? + # if @review.merchant_id == session[:merchant]["id"] + # flash[:status] = :failure + # flash[:result_text] = "Owner can not edit the review of the product!" + # redirect_to product_path(@review.product_id) + # end + # end + # end + # + # def check_for_product_owner_nested + # @product = Product.find_by(id: params[:product_id]) + # unless @product + # head :not_found + # end + # if !session[:merchant].nil? && @product.merchant_id == session[:merchant]["id"] + # flash[:status] = :failure + # flash[:result_text] = "Owner can not review the product!" + # redirect_to product_path(@product) + # end + # end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000000..05c9471d9e --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,26 @@ +module ApplicationHelper + + def money(float, small = false) + if small + length = float.to_s.length + if length > 12 + return "$%.1fb".html_safe % (float / 1000000000) + elsif length > 11 + return "$%.1fm".html_safe % (float / 1000000) + elsif length > 9 + return "$%.1fm".html_safe % (float / 1000000) + elsif length > 8 + return "$%.1fk".html_safe % (float / 1000) + elsif length > 6 + return "$%.1fk".html_safe % (float / 1000) + elsif length > 5 + return "$%.1i".html_safe % (float) + end + end + return "$%.2f" % float + end + + def days_ago(date) + (Date.today - date.to_date).to_i + end +end diff --git a/app/helpers/categories_helper.rb b/app/helpers/categories_helper.rb new file mode 100644 index 0000000000..e06f31554c --- /dev/null +++ b/app/helpers/categories_helper.rb @@ -0,0 +1,2 @@ +module CategoriesHelper +end diff --git a/app/helpers/main_helper.rb b/app/helpers/main_helper.rb new file mode 100644 index 0000000000..a062ab8119 --- /dev/null +++ b/app/helpers/main_helper.rb @@ -0,0 +1,5 @@ +module MainHelper + def total(array) + array.sum { |n| n.total } + end +end diff --git a/app/helpers/merchants_helper.rb b/app/helpers/merchants_helper.rb new file mode 100644 index 0000000000..5337747b0f --- /dev/null +++ b/app/helpers/merchants_helper.rb @@ -0,0 +1,2 @@ +module MerchantsHelper +end diff --git a/app/helpers/order_products_helper.rb b/app/helpers/order_products_helper.rb new file mode 100644 index 0000000000..fb2828b523 --- /dev/null +++ b/app/helpers/order_products_helper.rb @@ -0,0 +1,2 @@ +module OrderProductsHelper +end diff --git a/app/helpers/orders_helper.rb b/app/helpers/orders_helper.rb new file mode 100644 index 0000000000..443227fd48 --- /dev/null +++ b/app/helpers/orders_helper.rb @@ -0,0 +1,2 @@ +module OrdersHelper +end diff --git a/app/helpers/products_helper.rb b/app/helpers/products_helper.rb new file mode 100644 index 0000000000..ab5c42b325 --- /dev/null +++ b/app/helpers/products_helper.rb @@ -0,0 +1,2 @@ +module ProductsHelper +end diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 0000000000..a009ace51c --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,2 @@ +class ApplicationJob < ActiveJob::Base +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb new file mode 100644 index 0000000000..286b2239d1 --- /dev/null +++ b/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: 'from@example.com' + layout 'mailer' +end diff --git a/app/models/application_record.rb b/app/models/application_record.rb new file mode 100644 index 0000000000..10a4cba84d --- /dev/null +++ b/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end diff --git a/app/models/category.rb b/app/models/category.rb new file mode 100644 index 0000000000..d5930b23ce --- /dev/null +++ b/app/models/category.rb @@ -0,0 +1,5 @@ +class Category < ApplicationRecord + has_and_belongs_to_many :products + + validates :name, presence: true, uniqueness: { case_sensitive: false } +end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/models/merchant.rb b/app/models/merchant.rb new file mode 100644 index 0000000000..fc44cd31dd --- /dev/null +++ b/app/models/merchant.rb @@ -0,0 +1,47 @@ +class Merchant < ApplicationRecord + has_many :reviews + has_many :products + has_many :orders + has_many :order_products, through: :orders + + validates :username, presence: true, uniqueness: true + validates :email, presence: true, uniqueness:true + validates_format_of :email, :with => /\A[\w|.|+]+@[\w|.|+]+\.[\w|.|+]+\z/ + validates :oauth_provider, presence: true + validates :oauth_uid, presence: true, uniqueness: true + + def self.by_auth_hash(auth_hash) + merchant = Merchant.new + + merchant.oauth_provider = auth_hash['provider'] + merchant.oauth_uid = auth_hash['uid'] + merchant.email = auth_hash['info']['email'] + merchant.username = auth_hash['info']['nickname'] + + merchant + end + + def own_orders + OrderProduct.all.find_all {|op| op.product.merchant_id == id && op.order != nil} + end + + def pending_orders + own_orders.find_all { |o| o.status == "pending" } + end + + def shipped_orders + own_orders.find_all { |o| o.status == "shipped" } + end + + def total_revenue + shipped_orders.sum { |o| o.total } + end + + def active_products + products.find_all { |p| p.quantity > 0 } + end + + def inactive_products + products.find_all { |p| p.quantity == 0 } + end +end diff --git a/app/models/order.rb b/app/models/order.rb new file mode 100644 index 0000000000..0e4ab27d82 --- /dev/null +++ b/app/models/order.rb @@ -0,0 +1,53 @@ +class Order < ApplicationRecord + belongs_to :merchant, optional: true + has_many :order_products + has_many :products, through: :order_products + + validates :cust_cc, :cust_cc_exp, :cust_addr, :cust_email, :status, :cust_name, :cvv, :zip_code, presence: true + validate :quantity_check + validates_format_of :cust_email, :with => /\A\w+@\w+\.\w+\z/ + + def total + total = 0 + order_products.each do |order_product| + total += order_product.total + end + return total + end + + def quantity_check + order_products.each do |order_product| + available = order_product.product.quantity + desired = order_product.quantity + if desired > available + errors.add("#{order_product.product.name} does not have enough stock") + end + end + end + + def decrement_products + order_products.each do |order_product| + quantity = order_product.quantity + product = Product.find(order_product.product_id) + product.quantity -= quantity + product.save + end + end + + def increment_products + order_products.each do |order_product| + quantity = order_product.quantity + product = Product.find(order_product.product_id) + product.quantity += quantity + product.save + end + end + + def order_status + result = order_products.all? do |order_product| + order_product.status == "shipped" + end + self.status = "complete" if result + self.save + end +end diff --git a/app/models/order_product.rb b/app/models/order_product.rb new file mode 100644 index 0000000000..aee4465872 --- /dev/null +++ b/app/models/order_product.rb @@ -0,0 +1,22 @@ +class OrderProduct < ApplicationRecord + belongs_to :product + belongs_to :order, optional: true + + validates :quantity, presence: true, numericality: {greater_than: 0} + + def total + return product.price * quantity + end + + def self.find_in_cart(cart) + OrderProduct.all.find_all { |order_product| cart.include? order_product.id } unless cart.nil? + end + + def check_quantity + if product.quantity < quantity + return false + else + return true + end + end +end diff --git a/app/models/product.rb b/app/models/product.rb new file mode 100644 index 0000000000..76e5ca6356 --- /dev/null +++ b/app/models/product.rb @@ -0,0 +1,31 @@ +class Product < ApplicationRecord + has_many :order_products + has_many :reviews + has_many :orders, through: :order_products + belongs_to :merchant + has_and_belongs_to_many :categories + + validates :name, presence: true, uniqueness: true + validates :price, presence: true, numericality: {greater_than: 0} + validates :quantity, presence: true, numericality: {greater_than_or_equal_to: 0} + + def self.most_popular + @products = Product.where.not(quantity: 0).sort_by { |prod| -prod.orders.count }[0...6] + end + + def add_categories_by_params(params) + new_categories = params.keep_if{|key, value| key.include?'category'} + + new_categories.values.each do |cat| + category = Category.find_by(id: cat) + if category.nil? + errors.add(:categories, 'doesn\'t exist') + return false + end + self.categories << category unless self.categories.include? category + end + + return self.save + end + +end diff --git a/app/models/review.rb b/app/models/review.rb new file mode 100644 index 0000000000..f2e56f2d14 --- /dev/null +++ b/app/models/review.rb @@ -0,0 +1,26 @@ +class Review < ApplicationRecord + belongs_to :merchant, optional: true + belongs_to :product + + validates :rating, presence: true, numericality: { only_integer: true }, inclusion: { in: 1..5 } + + def self.get_rating_stars(rating) + stars = "" + + return stars if !(rating.is_a? Numeric) + + if rating <= 1 + stars = "⭑⭒⭒⭒⭒" + elsif rating <= 2 + stars = "⭑⭑⭒⭒⭒" + elsif rating <= 3 + stars = "⭑⭑⭑⭒⭒" + elsif rating <= 4 + stars = "⭑⭑⭑⭑⭒" + else + stars = "⭑⭑⭑⭑⭑" + end + + return stars + end +end diff --git a/app/reviews_helper.rb b/app/reviews_helper.rb new file mode 100644 index 0000000000..682b7b1abc --- /dev/null +++ b/app/reviews_helper.rb @@ -0,0 +1,2 @@ +module ReviewsHelper +end diff --git a/app/views/categories/add.html.erb b/app/views/categories/add.html.erb new file mode 100644 index 0000000000..7488d5498a --- /dev/null +++ b/app/views/categories/add.html.erb @@ -0,0 +1,47 @@ +
+
+
+
+
+
+
+

Categories

+ +
+

+ Select all that apply: +

+ <%= form_tag add_categories_path, :method => :post, id: @product.id do %> + <% @categories.each do |category| %> + <% unless @product.categories.include? category %> + + <% cat_name = "category_#{category.name}" %> + <%= check_box_tag cat_name, category.id, false, class: "check-with-label" %> + <%= label_tag cat_name, category.name, class: "label-for-check form-item" %> + + <% end %> + <% end %> +
+ <%= submit_tag "Add Selected", class: 'button center burg-btn cat-btn' %> +
+ <% end %> +
+ +
+ <%= form_tag categories_path, :method => :post do %> +

+ New category: +

+
+ + <%= text_field_tag :category_name, "", class: "add-category" %> + <%= hidden_field_tag :product_id, @product.id, class: "add-category" %> + + <%= submit_tag "Create", class: "add-category button burg-btn" %> +
+ <% end %> +
+ +
+
+
diff --git a/app/views/layouts/_error-messages.html.erb b/app/views/layouts/_error-messages.html.erb new file mode 100644 index 0000000000..88da4b4d70 --- /dev/null +++ b/app/views/layouts/_error-messages.html.erb @@ -0,0 +1,14 @@ +
+
<%= flash[:message] if flash[:message] %>
+ <% if flash[:details] %> + + <% end %> +
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000000..70bd81a3bc --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,103 @@ + + + + + + + <%= content_for?(:title) ? yield(:title) : "LoreStore" %> + + <%= stylesheet_link_tag "application" %> + <%= javascript_include_tag "application", 'data-turbolinks-track' => true %> + <%= csrf_meta_tags %> + + + +
+
+

+ <%= image_tag("008-spellbook.png", class:'tiny-img') %> <%= link_to "LoreStore", root_path %> +

+ +
+
+ <%= render partial: 'layouts/error-messages' if flash[:status] %> +
+ <%= yield %> +
+ + + diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb new file mode 100644 index 0000000000..cbd34d2e9d --- /dev/null +++ b/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb new file mode 100644 index 0000000000..37f0bddbd7 --- /dev/null +++ b/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/app/views/main/index.html.erb b/app/views/main/index.html.erb new file mode 100644 index 0000000000..c4c42b58c4 --- /dev/null +++ b/app/views/main/index.html.erb @@ -0,0 +1,9 @@ +
+
+

+ Welcome to +
LoreStore +

+

Your one-stop shop for all things magical or otherworldly

+
+
diff --git a/app/views/main/shopping_cart.html.erb b/app/views/main/shopping_cart.html.erb new file mode 100644 index 0000000000..87f2e31043 --- /dev/null +++ b/app/views/main/shopping_cart.html.erb @@ -0,0 +1,54 @@ +
+

+ <%= image_tag("014-coffin.png", class:'tiny-img') %> Shopping Cart +

+ + <% if @products %> + <% @products.each do |order_product|%> + <%= form_for order_product do |f| %> +
+ + > + +
+
+

<%= order_product.product.name %>

+
+
+
+

Price: + <%= money(order_product.product.price) %> +

+
+
+ <%= f.label :quantity, "Quantity:", class: 'shopping-cart-form' %> + <%= f.text_field :quantity, class: 'shopping-cart-form' %> +
+
+

Total: + <%= money(order_product.total) %> +

+
+
+ <%= f.submit "Update Item", class: 'button burg-btn small-5 columns' %> + <%= link_to "Remove From Cart", order_product_path(order_product.id), method: :delete, data: { confirm: "Are you sure?" }, class: "button burg-btn delete-button small-7 columns" %> +
+
+ +
+ <% end %> + <% end %> + <% end %> + + <% unless @products.nil? || @products.empty? %> +
+

Cart Total: <%= money(total(@products)) %>

+ + <%= link_to "Checkout Cart", new_order_path, class: 'button burg-btn small-12 columns' %> +
+ <% else %> +

+ Your cart is empty!! 👻 +

+ <% end %> +
diff --git a/app/views/merchants/_list-merchant-products.html.erb b/app/views/merchants/_list-merchant-products.html.erb new file mode 100644 index 0000000000..917c2d57ad --- /dev/null +++ b/app/views/merchants/_list-merchant-products.html.erb @@ -0,0 +1,19 @@ +
+ <% if products.count == 0 %> + +

This merchant has no products.

+ <% else %> + <% products.each do |product| %> +
+ <%= link_to product_path(product.id) do %> +
+ >
+

+ <%= product.name %> +

+ <% end %> +
+ <% end %> + <% end %> +
diff --git a/app/views/merchants/completed.html.erb b/app/views/merchants/completed.html.erb new file mode 100644 index 0000000000..3b7505fc59 --- /dev/null +++ b/app/views/merchants/completed.html.erb @@ -0,0 +1,47 @@ +
+

+ <%= image_tag("002-eye.png", class:'tiny-img') %> Completed Orders +

+ + <% if @order_products.empty? %> +
+

You have no completed orders at this time!

+
+ <% else %> + <% @order_products.each do |op| %> +
+ + > + + +
+

<%= link_to op.product.name, product_path(op.product.id) %>

+

Quantity: <%= op.quantity %>

+

Price: <%= money(op.product.price) %>

+

Total: <%= money(op.total) %>

+
+ +
+
+ +

+ Customer: <%= op.order.cust_name %> +

+

+ Order Placed: <%= op.order.created_at.strftime('%B %d, %Y') %> +

+

+ Order Status: Completed +

+ +

+ <%= op.order.cust_addr %> +

+
+
+
+ +
+ <% end %> + <% end %> +
diff --git a/app/views/merchants/edit.html.erb b/app/views/merchants/edit.html.erb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/views/merchants/inventory.html.erb b/app/views/merchants/inventory.html.erb new file mode 100644 index 0000000000..e730bf78c9 --- /dev/null +++ b/app/views/merchants/inventory.html.erb @@ -0,0 +1,78 @@ +
+

+ <%= image_tag("003-pumpkin.png", class:'tiny-img') %> Inventory Management +

+

Active Products

+ <% if @products.empty? %> +
+

You are not currently selling any items!

+
+ + <% else %> +
+ <% @products.each do |product| %> + <%= form_for product do |f| %> +
+ + > + +
+

<%= product.name %>

+
+
+ <%= f.label :price, class: 'inventory-form medium-6 large-6 columns' %> + <%= f.text_field :price, class: 'inventory-form medium-6 large-6' %> +
+
+ <%= f.label :quantity, class: 'inventory-form medium-6 large-6 columns' %> + <%= f.text_field :quantity, class: 'inventory-form medium-6 large-6 columns' %> +
+
+ <%= f.submit "Update", class: 'button burg-btn small-12 medium-6 columns' %> + <%= link_to "Retire", retire_product_path(product.id), class: 'button burg-btn small-12 medium-5 columns' %> +
+
+ +
+ <% end %> + <% end %> +
+ <% end %> + + <%= link_to "Sell A New Product", new_product_path, class: 'button new-product-button' %> + + <% unless @inactive_products.empty? %> +
+

Inactive Products

+ <% @inactive_products.each do |product| %> + <%= form_for product do |f| %> +
+ + > + +
+

<%= product.name %>

+
+
+ <%= f.label :price, class: 'inventory-form' %> + <%= f.text_field :price, class: 'inventory-form' %> +
+
+ <%= f.label :quantity, class: 'inventory-form' %> + <%= f.text_field :quantity, class: 'inventory-form' %> +
+
+ <%= f.submit "Update", class: 'button burg-btn' %> +
+
+ +
+ <% end %> + <% end %> +
+ <% end %> + +
+ +
+
diff --git a/app/views/merchants/new.html.erb b/app/views/merchants/new.html.erb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/views/merchants/pending.html.erb b/app/views/merchants/pending.html.erb new file mode 100644 index 0000000000..b8c766a58d --- /dev/null +++ b/app/views/merchants/pending.html.erb @@ -0,0 +1,49 @@ +
+

+ <%= image_tag("004-torch.png", class:'tiny-img') %> Pending Orders +

+ + <% if @order_products.empty? %> +
+

You have no pending orders at this time!

+
+ <% else %> + <% @order_products.each do |op| %> +
+ + > + + +
+

<%= link_to op.product.name, product_path(op.product.id) %>

+

Quantity: <%= op.quantity %>

+

Price: <%= money(op.product.price) %>

+

Total: <%= money(op.total) %>

+
+ +
+
+ +

+ Customer: <%= op.order.cust_name %> +

+

+ Order Placed: <%= op.order.created_at.strftime('%B %d, %Y') %> +

+

+ Order Status: Pending +

+ +

+ <%= op.order.cust_addr %> +

+ + <%= link_to "Mark Shipped", mark_shipped_path(op.id), class: 'button burg-btn medium-12 columns ship-btn' %> + +
+
+
+
+ <% end %> + <% end %> +
diff --git a/app/views/merchants/revenue.html.erb b/app/views/merchants/revenue.html.erb new file mode 100644 index 0000000000..d0e23653cb --- /dev/null +++ b/app/views/merchants/revenue.html.erb @@ -0,0 +1,69 @@ +
+

+ <%= image_tag("030-brain.png", class:'tiny-img') %> Earnings Details +

+

Total Revenue to Date: <%= money(@user.total_revenue) %>

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Order Category + + # Orders + + Average Income per Order + + Total Income +
+ Completed + + <%= @completed.count %> + + <%= money(@completed_average) %> + + <%= money(@completed.sum{|order| order.total}) %> +
+ Total + + <%= @all.count %> + + <%= money(@all_average) %> + + <%= money(@all.sum{|order| order.total}) %> +
+ Pending + + <%= @pending.count %> + + <%= money(@pending_average) %> + + <%= money(@pending.sum{|order| order.total}) %> +
+
diff --git a/app/views/merchants/show.html.erb b/app/views/merchants/show.html.erb new file mode 100644 index 0000000000..0be7328b9d --- /dev/null +++ b/app/views/merchants/show.html.erb @@ -0,0 +1,11 @@ +
+

+ <%= image_tag("016-skull.png", class:'tiny-img') %> <%=@merchant.username%> +

+ Contact Merchant +

Products

+ +
+ <%= render partial: "list-merchant-products", locals: {products: @merchant.products} %> +
+
diff --git a/app/views/merchants/summary.html.erb b/app/views/merchants/summary.html.erb new file mode 100644 index 0000000000..6c414d9125 --- /dev/null +++ b/app/views/merchants/summary.html.erb @@ -0,0 +1,74 @@ +
+

+ <%= image_tag("029-ghost.png", class:'tiny-img') %> Welcome, <%= @user.username %>! +

+ +
+ +
+ +
+
+
+

Shopping Cart

+
+ <%= session[:cart].count %> + <%= link_to "Go To Cart", shopping_cart_path, class: 'button burg-btn profile-button' %> +
+
+ +
+
+
+

Currently Selling

+
+ <%= @user.active_products.count %> + <%= link_to "Inventory Management", self_inventory_path, class: 'button burg-btn profile-button' %> +
+
+ +
+
+
+

Pending Orders

+
+ "><%= @user.pending_orders.count %> + <%= link_to "Pending Orders", self_pending_path, class: 'button burg-btn profile-button' %> +
+
+ +
+
+
+

Purchase History

+
+ <%= @user.orders.count %> + <%= link_to "Past Orders", orders_path, class: 'button burg-btn profile-button' %> +
+
+ +
+
+
+

Total Revenue

+
+ <%= money(@user.total_revenue, true) %> + <%= link_to "Income Details", self_revenue_path, class: 'button burg-btn profile-button' %> +
+
+ +
+
+
+

Completed Orders

+
+ <%= @user.shipped_orders.count %> + <%= link_to "Completed Orders", self_completed_path, class: 'button burg-btn profile-button' %> +
+
+ +
+ + +
+
diff --git a/app/views/order_products/index.html.erb b/app/views/order_products/index.html.erb new file mode 100644 index 0000000000..7130d2e0cc --- /dev/null +++ b/app/views/order_products/index.html.erb @@ -0,0 +1,42 @@ +
+

+ <%= image_tag("028-heart.png", class:'tiny-img') %> Sold Items +

+ + <% @order_products.each do |op| %> +
+ + > + + +
+

<%= op.product.name %>

+

Quantity: <%= op.quantity %>

+

Price: <%= money(op.product.price) %>

+

Total: <%= money(op.total) %>

+
+ +
+
+ +

+ Customer: <%= op.order.cust_name %> +

+

+ <%= op.order.cust_addr %> +

+
+ + + <% if op.status == 'shipped' %> + Completed + <% else %> + <%= link_to "Mark Shipped", root_path, class: 'button medium-12 columns ship-btn' %> + <% end %> + +
+
+ +
+ <% end %> +
diff --git a/app/views/orders/_form.html.erb b/app/views/orders/_form.html.erb new file mode 100644 index 0000000000..ba81f63be1 --- /dev/null +++ b/app/views/orders/_form.html.erb @@ -0,0 +1,45 @@ +<%= form_for @order, html: {id: "order-form"} do |f| %> + <% if session[:merchant] %> + <%= f.hidden_field :merchant_id, value: session[:merchant]['id'] %> + <% end %> + <%= f.hidden_field :status, value: "pending" %> + + <%= content_tag :div, class: "name-wrapper form-row row" do %> + <%= f.label :cust_name, "Name", class: "prod-form-item large-3 medium-3 small-12 columns" %> + <%= f.text_field :cust_name , class: "prod-form-item large-3 medium-3 small-12 columns" %> + <% end %> + + <%= content_tag :div, class: "cc-wrapper form-row row" do %> + <%= f.label :cust_cc, "Credit Card Number" , class: "prod-form-item large-3 medium-3 small-12 columns" %> + <%= f.text_field :cust_cc , class: "prod-form-item large-3 medium-3 small-12 columns" %> + <% end %> + + <%= content_tag :div, class: "ccexp-wrapper form-row row" do %> + <%= f.label :cust_cc_exp, "Expiration Date" , class: "prod-form-item large-3 medium-3 small-12 columns" %> + <%= f.text_field :cust_cc_exp , class: "prod-form-item large-3 medium-3 small-12 columns" %> + <% end %> + + <%= content_tag :div, class: "cvv-wrapper form-row row" do %> + <%= f.label :cvv, "cvv" , class: "prod-form-item large-3 medium-3 small-12 columns" %> + <%= f.text_field :cvv , class: "prod-form-item large-3 medium-3 small-12 columns" %> + <% end %> + + <%= content_tag :div, class: "addr-wrapper form-row row" do %> + <%= f.label :cust_addr, "Address" , class: "prod-form-item large-3 medium-3 small-12 columns" %> + <%= f.text_field :cust_addr , class: "prod-form-item large-3 medium-3 small-12 columns" %> + <% end %> + + <%= content_tag :div, class: "zip-wrapper form-row row" do %> + <%= f.label :zip_code, "Zip Code" , class: "prod-form-item large-3 medium-3 small-12 columns" %> + <%= f.text_field :zip_code , class: "prod-form-item large-3 medium-3 small-12 columns" %> + <% end %> + + <%= content_tag :div, class: "email-wrapper form-row row" do %> + <%= f.label :cust_email, "Email" , class: "prod-form-item large-3 medium-3 small-12 columns" %> + <%= f.text_field :cust_email , class: "prod-form-item large-3 medium-3 small-12 columns" %> + <% end %> + + <%= content_tag :div, class: "submit-wrapper form-row row align-center" do %> + <%= f.submit button_text, class: "button burg-btn medium-6 small-12 columns product-form" %> + <% end %> +<% end %> diff --git a/app/views/orders/confirmation.html.erb b/app/views/orders/confirmation.html.erb new file mode 100644 index 0000000000..efcf77c90f --- /dev/null +++ b/app/views/orders/confirmation.html.erb @@ -0,0 +1,48 @@ +
+

+ <%= image_tag("018-potful.png", class:'tiny-img') %> Order confirmation +

+ +
+

Thanks for your purchase!

+
+
    +
  • Order Number: #<%= @order.id %>
  • +
  • Total: $ <%= @order.total %>
  • +
  • Date: <%= @order.created_at.strftime("%B %d, %Y") %>
  • +
  • Status: <%= @order.status.capitalize %>
  • +
+
+
+ +

Order details

+
+ + + + + + + + <% @order.order_products.each do |order_product| %> + + + + + + <% end %> + +
QuantityItemTotal
<%= order_product.quantity %> +
+ <%= link_to product_path(order_product.product) do %> + + <% end %> +
+
+

<%= link_to order_product.product.name, product_path(id:order_product.product.id) %>

+

Sold by <%= order_product.product.merchant.username %>

+

$ <%= sprintf('%.2f', order_product.product.price) %>

+
+
$ <%= sprintf('%.2f', order_product.total) %>
+
+
diff --git a/app/views/orders/index.html.erb b/app/views/orders/index.html.erb new file mode 100644 index 0000000000..ea2c8e17ee --- /dev/null +++ b/app/views/orders/index.html.erb @@ -0,0 +1,56 @@ +
+

+ <%= image_tag("001-zombie.png", class:'tiny-img') %> Order history +

+ + <% if @orders == nil %> +

You haven't purchased anything yet!

+ + <% else %> + <% @orders.each do |order| %> +
+ +
+
+

ORDER PLACED

+

<%= order.created_at.strftime("%B %d, %Y") %>

+

TOTAL

+

$ <%= sprintf('%.2f', order.total) %>

+

STATUS

+

<%= order.status.capitalize %>

+

ORDER #<%= order.id %>

+

<%= link_to "Order Details", order_path(order.id), class: 'button burg-btn'%>

+
+
+ +
+ <% order.order_products.each do | order_product | %> +
+
+ <%= link_to product_path(order_product.product) do %> + + <% end %> +
+
+

+ <%= order_product.product.name %> +

+

+ + Price: $ <%= sprintf('%.2f', order_product.product.price) %> + + + Quantity: <%= order_product.quantity %> + +

+

+ Subtotal: $ <%= sprintf('%.2f', order_product.total) %> +

+
+
+ <% end %> +
+
+ <% end %> + <% end %> +
diff --git a/app/views/orders/new.html.erb b/app/views/orders/new.html.erb new file mode 100644 index 0000000000..d92bd3e7a8 --- /dev/null +++ b/app/views/orders/new.html.erb @@ -0,0 +1,6 @@ +
+

+ <%= image_tag("005-trident.png", class:'tiny-img') %> Payment details +

+ <%= render partial: "form", locals: { hide_placeholder_text: true, button_text: "Place Order" }%> +
diff --git a/app/views/orders/show.html.erb b/app/views/orders/show.html.erb new file mode 100644 index 0000000000..eeb46ee7bf --- /dev/null +++ b/app/views/orders/show.html.erb @@ -0,0 +1,87 @@ +
+

+ <%= image_tag("025-grave.png", class:'tiny-img') %> Order details +

+ +
+
+
+

Order information

+

Ordered on <%= @order.created_at.strftime("%B %d, %Y") %>

+

Order #<%= @order.id %>

+

Status: <%= @order.status.capitalize %>

+ <%= link_to "Cancel Order", cancel_path(@order), class: 'button burg-btn' %> +
+ +
+

Payment by

+

+ + + + + <%= @order.cust_name %> +

+

+ + + + + <%= @order.cust_email %> +

+

+ + + + + <%= @order.cust_addr %> +

+

+ + + + + ****<%= @order.cust_cc.last(4) %> (expires <%= @order.cust_cc_exp %>) +

+
+
+ +
+ + + + + + + + <% @order.order_products.each do |order_product| %> + + + + + + <% end %> + +
QuantityItemTotal
<%= order_product.quantity %> +
+ <%= link_to product_path(order_product.product) do %> + + <% end %> +
+
+

+ <%= link_to order_product.product.name, product_path(id:order_product.product.id) %> +

+

+ Sold by <%= order_product.product.merchant.username %> +

+

+ $ <%= sprintf('%.2f', order_product.product.price) %> +

+
+
+ $ <%= sprintf('%.2f', order_product.total) %> +
+
+
+
diff --git a/app/views/products/_product-form.html.erb b/app/views/products/_product-form.html.erb new file mode 100644 index 0000000000..6192ce982b --- /dev/null +++ b/app/views/products/_product-form.html.erb @@ -0,0 +1,30 @@ +<%= form_for @product do |f| %> +
+ <%= f.label :name, "Product Name", class: "prod-form-item large-3 medium-3 small-12 columns" %> + <%= f.text_field :name, class: "prod-form-item large-8 small-12 columns" %> +
+ +
+ <%= f.label :image_url, "Image URL", class: "prod-form-item large-3 medium-3 small-12 columns" %> + <%= f.text_field :image_url, class: "prod-form-item large-8 medium-6 small-12 columns" %> +
+ +
+ <%= f.label :price, class: "prod-form-item large-3 medium-3 small-12 columns" %> + <%= f.text_field :price, class: "prod-form-item large-8 medium-6 small-12 columns" %> +
+ +
+ <%= f.label :quantity, class: "prod-form-item large-3 medium-3 small-12 columns" %> + <%= f.text_field :quantity, class: "prod-form-item large-8 medium-6 small-12 columns" %> +
+ +
+ <%= f.label :description, class: "prod-form-item large-3 medium-3 small-12 columns" %> + <%= f.text_area :description, class: "prod-form-item large-12 medium-6 small-12 columns" %> +
+ +
+ <%= f.submit button, class: 'button burg-btn medium-6 small-12 columns product-form' %> +
+<% end %> diff --git a/app/views/products/_product-list.html.erb b/app/views/products/_product-list.html.erb new file mode 100644 index 0000000000..237071af6c --- /dev/null +++ b/app/views/products/_product-list.html.erb @@ -0,0 +1,14 @@ +
+ <% products.each do |product| %> +
+ <%= link_to product_path(product.id) do %> +
+ >
+

+ <%= product.name %> +

+ <% end %> +
+ <% end %> +
diff --git a/app/views/products/_review.html.erb b/app/views/products/_review.html.erb new file mode 100644 index 0000000000..bd874576cf --- /dev/null +++ b/app/views/products/_review.html.erb @@ -0,0 +1,24 @@ +
+
+
+ <%if review.merchant%> + <%= review.merchant.username%>: + <%else%> + Guest + <%end%> + Rating: + + <%= Review.get_rating_stars(review.rating) %> + + + <%= review.updated_at.strftime("%b %d, %Y") %> +
+

<%= review.description %>

+
+ + <% if session[:merchant] && session[:merchant]['id'] == review.merchant_id%> +
<%= link_to "Edit", edit_review_path(review.id) %>
+
<%= link_to "Delete", review_path(review.id), method: :delete, data: { confirm: "Are you sure?" }%>
+ <% end %> + +
diff --git a/app/views/products/edit.html.erb b/app/views/products/edit.html.erb new file mode 100644 index 0000000000..9e7ee11bc6 --- /dev/null +++ b/app/views/products/edit.html.erb @@ -0,0 +1,7 @@ +
+

+ <%= image_tag("006-poison.png", class:'tiny-img') %> Edit Product Info +

+ + <%= render partial: "product-form", locals: {button: "Update"} %> +
diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb new file mode 100644 index 0000000000..7f34addc9f --- /dev/null +++ b/app/views/products/index.html.erb @@ -0,0 +1,23 @@ +
+ + +
+

+ <%= image_tag("007-castle.png", class:'tiny-img') %> <%= @title %> +

+ + <%= render partial: "product-list", locals: {products: @products} %> +
+
diff --git a/app/views/products/new.html.erb b/app/views/products/new.html.erb new file mode 100644 index 0000000000..17aab8313e --- /dev/null +++ b/app/views/products/new.html.erb @@ -0,0 +1,7 @@ +
+

+ <%= image_tag("009-candelabra.png", class:'tiny-img') %> Create New Product +

+ + <%= render partial: "product-form", locals: {button: "Create"} %> +
diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb new file mode 100644 index 0000000000..df0ee11e21 --- /dev/null +++ b/app/views/products/show.html.erb @@ -0,0 +1,63 @@ +
+
+
> +
+
+

+ <%= @product.name %> + <%= (link_to "Edit", edit_product_path(@product.id), class: 'right button readable') if @own_product %> +

+

From seller + <%= link_to @product.merchant.username, merchant_path(@product.merchant_id) %> +

+

<%= money(@product.price) %>

+ <%= form_tag do %> + <%= label_tag :quantity, "Quantity:", class: 'small-6 columns' %> + <%= select_tag :quantity, options_for_select((1..@product.quantity)), class: 'small-6 columns' %> + <%= submit_tag "Add to Cart", class: 'button cart-btn' %> + <% end %> +
+
+
+

Description:

+

+ <%= @product.description %> +

+
+
+ +
+ +
+ + <% unless @product.reviews.empty?%> +

Average Review: + <% rating = @product.reviews.average(:rating) %> + <%= Review.get_rating_stars(rating) %> +

+ <% end %> + <%= link_to new_product_review_path(@product.id) do %> + + Add Review + + <% end %> +
+ +
+ + <% @product.reviews.each do |review| %> + <%= render partial: 'review', locals: {review: review} %> + <% end %> +
+
diff --git a/app/views/reviews/_review_form.html.erb b/app/views/reviews/_review_form.html.erb new file mode 100644 index 0000000000..a14704f211 --- /dev/null +++ b/app/views/reviews/_review_form.html.erb @@ -0,0 +1,24 @@ +
+
+ <%= form_for @review do |f| %> + +
Please rate the product:
+
+ <%= f.radio_button(:rating, "5") %> <%= f.label(:rating.to_s + "_5") %> + <%= f.radio_button(:rating, "4") %> <%= f.label(:rating.to_s + "_4") %> + <%= f.radio_button(:rating, "3") %> <%= f.label(:rating.to_s + "_3") %> + <%= f.radio_button(:rating, "2") %> <%= f.label(:rating.to_s + "_2") %> + <%= f.radio_button(:rating, "1") %> <%= f.label(:rating.to_s + "_1") %> +
+ +
+ <%= f.label :description %> + <%= f.text_field :description %> +
+ <%= f.hidden_field :product_id, value: @review.product_id %> + + <%= f.submit button, class: 'button submit_review' %> + + <% end %> +
+
diff --git a/app/views/reviews/edit.html.erb b/app/views/reviews/edit.html.erb new file mode 100644 index 0000000000..b22d4391db --- /dev/null +++ b/app/views/reviews/edit.html.erb @@ -0,0 +1,6 @@ +
+

+ <%= image_tag("012-owl.png", class:'tiny-img') %> Payment details +

+ <%= render partial: "review_form", locals: {button: "Submit review"} %> +
diff --git a/app/views/reviews/new.html.erb b/app/views/reviews/new.html.erb new file mode 100644 index 0000000000..844be5df4f --- /dev/null +++ b/app/views/reviews/new.html.erb @@ -0,0 +1,6 @@ +
+

+ <%= image_tag("019-crescent-moon.png", class:'tiny-img') %> Payment details +

+ <%= render partial: "review_form", locals: {button: "Submit review"} %> +
diff --git a/bin/bundle b/bin/bundle new file mode 100755 index 0000000000..66e9889e8b --- /dev/null +++ b/bin/bundle @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +load Gem.bin_path('bundler', 'bundle') diff --git a/bin/rails b/bin/rails new file mode 100755 index 0000000000..fb5757471e --- /dev/null +++ b/bin/rails @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end +APP_PATH = File.expand_path('../config/application', __dir__) +require_relative '../config/boot' +require 'rails/commands' + +if ENV['RAILS_ENV'] == 'test' + require 'simplecov' + SimpleCov.start 'rails' + puts "required simplecov" +end diff --git a/bin/rake b/bin/rake new file mode 100755 index 0000000000..d87d5f5781 --- /dev/null +++ b/bin/rake @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000000..78c4e861dc --- /dev/null +++ b/bin/setup @@ -0,0 +1,38 @@ +#!/usr/bin/env ruby +require 'pathname' +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a starting point to setup your application. + # Add necessary setup steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') + + + # puts "\n== Copying sample files ==" + # unless File.exist?('config/database.yml') + # cp 'config/database.yml.sample', 'config/database.yml' + # end + + puts "\n== Preparing database ==" + system! 'bin/rails db:setup' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/bin/spring b/bin/spring new file mode 100755 index 0000000000..fb2ec2ebb4 --- /dev/null +++ b/bin/spring @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby + +# This file loads spring without using Bundler, in order to be fast. +# It gets overwritten when you run the `spring binstub` command. + +unless defined?(Spring) + require 'rubygems' + require 'bundler' + + lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) + spring = lockfile.specs.detect { |spec| spec.name == "spring" } + if spring + Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path + gem 'spring', spring.version + require 'spring/binstub' + end +end diff --git a/bin/update b/bin/update new file mode 100755 index 0000000000..a8e4462f20 --- /dev/null +++ b/bin/update @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +require 'pathname' +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a way to update your development environment automatically. + # Add necessary update steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + puts "\n== Updating database ==" + system! 'bin/rails db:migrate' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/bin/yarn b/bin/yarn new file mode 100755 index 0000000000..c2bacef836 --- /dev/null +++ b/bin/yarn @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +VENDOR_PATH = File.expand_path('..', __dir__) +Dir.chdir(VENDOR_PATH) do + begin + exec "yarnpkg #{ARGV.join(" ")}" + rescue Errno::ENOENT + $stderr.puts "Yarn executable was not detected in the system." + $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" + exit 1 + end +end diff --git a/config.ru b/config.ru new file mode 100644 index 0000000000..f7ba0b527b --- /dev/null +++ b/config.ru @@ -0,0 +1,5 @@ +# This file is used by Rack-based servers to start the application. + +require_relative 'config/environment' + +run Rails.application diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 0000000000..b1dc090fe1 --- /dev/null +++ b/config/application.rb @@ -0,0 +1,26 @@ +require_relative 'boot' + +require 'rails/all' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module Betsy + class Application < Rails::Application + config.generators do |g| + # Force new test files to be generated in the minitest-spec style + g.test_framework :minitest, spec: true + + # Always use .js files, never .coffee + g.javascript_engine :js + end + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 5.1 + + config.serve_static_assets = true + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + end +end diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000000..30f5120df6 --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,3 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/config/cable.yml b/config/cable.yml new file mode 100644 index 0000000000..3cba994bb2 --- /dev/null +++ b/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: async + +production: + adapter: redis + url: redis://localhost:6379/1 + channel_prefix: betsy_production diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 0000000000..6903bb6083 --- /dev/null +++ b/config/database.yml @@ -0,0 +1,85 @@ +# PostgreSQL. Versions 9.1 and up are supported. +# +# Install the pg driver: +# gem install pg +# On OS X with Homebrew: +# gem install pg -- --with-pg-config=/usr/local/bin/pg_config +# On OS X with MacPorts: +# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config +# On Windows: +# gem install pg +# Choose the win32 build. +# Install PostgreSQL and put its /bin directory on your path. +# +# Configure Using Gemfile +# gem 'pg' +# +default: &default + adapter: postgresql + encoding: unicode + # For details on connection pooling, see Rails configuration guide + # http://guides.rubyonrails.org/configuring.html#database-pooling + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + +development: + <<: *default + database: betsy_development + + # The specified database role being used to connect to postgres. + # To create additional roles in postgres see `$ createuser --help`. + # When left blank, postgres will use the default role. This is + # the same name as the operating system user that initialized the database. + #username: betsy + + # The password associated with the postgres role (username). + #password: + + # Connect on a TCP socket. Omitted by default since the client uses a + # domain socket that doesn't need configuration. Windows does not have + # domain sockets, so uncomment these lines. + #host: localhost + + # The TCP port the server listens on. Defaults to 5432. + # If your server runs on a different port number, change accordingly. + #port: 5432 + + # Schema search path. The server defaults to $user,public + #schema_search_path: myapp,sharedapp,public + + # Minimum log levels, in increasing order: + # debug5, debug4, debug3, debug2, debug1, + # log, notice, warning, error, fatal, and panic + # Defaults to warning. + #min_messages: notice + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: betsy_test + +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%= ENV['DATABASE_URL'] %> +# +production: + <<: *default + database: betsy_production + username: betsy + password: <%= ENV['BETSY_DATABASE_PASSWORD'] %> diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000000..426333bb46 --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative 'application' + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 0000000000..5187e22186 --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,54 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable/disable caching. By default caching is disabled. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + config.action_mailer.perform_caching = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = true + + # Suppress logger output for asset requests. + config.assets.quiet = true + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true + + # Use an evented file watcher to asynchronously detect changes in source code, + # routes, locales, etc. This feature depends on the listen gem. + config.file_watcher = ActiveSupport::EventedFileUpdateChecker +end diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 0000000000..9284f84839 --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,91 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Attempt to read encrypted secrets from `config/secrets.yml.enc`. + # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or + # `config/secrets.yml.key`. + config.read_encrypted_secrets = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Mount Action Cable outside main process or domain + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment) + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "betsy_#{Rails.env}" + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 0000000000..8e5cbde533 --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,42 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + config.action_mailer.perform_caching = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb new file mode 100644 index 0000000000..89d2efab2b --- /dev/null +++ b/config/initializers/application_controller_renderer.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb new file mode 100644 index 0000000000..4b828e80cb --- /dev/null +++ b/config/initializers/assets.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join('node_modules') + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000000..59385cdf37 --- /dev/null +++ b/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb new file mode 100644 index 0000000000..5a6a32d371 --- /dev/null +++ b/config/initializers/cookies_serializer.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Specify a serializer for the signed and encrypted cookie jars. +# Valid options are :json, :marshal, and :hybrid. +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000000..4a994e1e7b --- /dev/null +++ b/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb new file mode 100644 index 0000000000..ac033bf9dc --- /dev/null +++ b/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb new file mode 100644 index 0000000000..dc1899682b --- /dev/null +++ b/config/initializers/mime_types.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb new file mode 100644 index 0000000000..fd4416122a --- /dev/null +++ b/config/initializers/omniauth.rb @@ -0,0 +1,3 @@ +Rails.application.config.middleware.use OmniAuth::Builder do + provider :github, ENV["GITHUB_CLIENT_ID"], ENV["GITHUB_CLIENT_SECRET"], scope: "user:email" +end diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb new file mode 100644 index 0000000000..bbfc3961bf --- /dev/null +++ b/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 0000000000..decc5a8573 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# 'true': 'foo' +# +# To learn more, please read the Rails Internationalization guide +# available at http://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/config/puma.rb b/config/puma.rb new file mode 100644 index 0000000000..1e19380dcb --- /dev/null +++ b/config/puma.rb @@ -0,0 +1,56 @@ +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers: a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum; this matches the default thread size of Active Record. +# +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked webserver processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. If you use this option +# you need to make sure to reconnect any threads in the `on_worker_boot` +# block. +# +# preload_app! + +# If you are preloading your application and using Active Record, it's +# recommended that you close any connections to the database before workers +# are forked to prevent connection leakage. +# +# before_fork do +# ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) +# end + +# The code in the `on_worker_boot` will be called if you are using +# clustered mode by specifying a number of `workers`. After each worker +# process is booted, this block will be run. If you are using the `preload_app!` +# option, you will want to use this block to reconnect to any threads +# or connections that may have been created at application boot, as Ruby +# cannot share connections between processes. +# +# on_worker_boot do +# ActiveRecord::Base.establish_connection if defined?(ActiveRecord) +# end +# + +# Allow puma to be restarted by `rails restart` command. +plugin :tmp_restart diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000000..cc8a00136b --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,43 @@ +Rails.application.routes.draw do + # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html + root "main#index" + get "/auth/:provider/callback", to: "merchants#login", as: 'login' + get '/logout', to: 'merchants#logout', as: 'logout' + resources :merchants, except: [:index, :new] do + resources :orders, only: [:index] + resources :order_products, only:[:index], as: 'sold' + end + + resources :orders + resources :order_products, only: [:update, :destroy] + get '/products/:id/categories', to: 'categories#add', as: 'add_categories' + post '/products/:id/categories', to: 'products#add_categories' + post '/products/:id/new_category', to: 'categories#create' + resources :products + resources :categories, only: [:new, :create, :destroy] do + resources :products, only: [:index] + end + + resources :products do + resources :reviews, only: [:new] + end + resources :reviews, only: [:create, :edit, :update, :destroy] + + post '/products/:id', to: 'main#add_to_cart', as: 'add_to_cart' + get '/cart', to: 'main#shopping_cart', as: 'shopping_cart' + + # User pages + get '/home', to: 'merchants#summary', as: 'self_summary' + get '/pending', to: 'merchants#pending', as: 'self_pending' + get '/completed', to: 'merchants#completed', as: 'self_completed' + get '/revenue', to: 'merchants#revenue', as: 'self_revenue' + get '/inventory', to: 'merchants#inventory', as: 'self_inventory' + + get '/:id/mark_shipped', to: 'merchants#mark_shipped', as: 'mark_shipped' + get '/:id/retire', to: 'products#retire', as: 'retire_product' + + get '/confirmation/:id', to: 'orders#confirmation', as: 'confirmation' + + # Order cancel + get '/:id/cancel', to: 'orders#cancel', as: 'cancel' +end diff --git a/config/secrets.yml b/config/secrets.yml new file mode 100644 index 0000000000..72b9e72e41 --- /dev/null +++ b/config/secrets.yml @@ -0,0 +1,32 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key is used for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! + +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +# You can use `rails secret` to generate a secure secret key. + +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. + +# Shared secrets are available across all environments. + +# shared: +# api_key: a1B2c3D4e5F6 + +# Environmental secrets are only available for that specific environment. + +development: + secret_key_base: d8797e95f21baa346e31bb4faa5dcea245a15a27f2ded487fc5d4ade2c6e74b160fb66be42573345b02d608424a56e6282b69803cb7236738bc220dd3cfa9905 + +test: + secret_key_base: 27c6cf01738639692c9961e53a4110dc2a2c272aed16e8db3e4dd109fd4d47f4d68a2008b12795a9b5b493ed29293839e838ed9ef597b85343941e1782a9f291 + +# Do not keep production secrets in the unencrypted secrets file. +# Instead, either read values from the environment. +# Or, use `bin/rails secrets:setup` to configure encrypted secrets +# and move the `production:` environment over there. + +production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> diff --git a/config/spring.rb b/config/spring.rb new file mode 100644 index 0000000000..c9119b40c0 --- /dev/null +++ b/config/spring.rb @@ -0,0 +1,6 @@ +%w( + .ruby-version + .rbenv-vars + tmp/restart.txt + tmp/caching-dev.txt +).each { |path| Spring.watch(path) } diff --git a/db/migrate/20171017230149_create_merchants.rb b/db/migrate/20171017230149_create_merchants.rb new file mode 100644 index 0000000000..262cacfc5e --- /dev/null +++ b/db/migrate/20171017230149_create_merchants.rb @@ -0,0 +1,9 @@ +class CreateMerchants < ActiveRecord::Migration[5.1] + def change + create_table :merchants do |t| + t.string :username + t.string :email + t.timestamps + end + end +end diff --git a/db/migrate/20171017230651_create_orders.rb b/db/migrate/20171017230651_create_orders.rb new file mode 100644 index 0000000000..8523638ae9 --- /dev/null +++ b/db/migrate/20171017230651_create_orders.rb @@ -0,0 +1,14 @@ +class CreateOrders < ActiveRecord::Migration[5.1] + def change + create_table :orders do |t| + t.string :cust_name + t.string :status + t.string :cust_email + t.string :cust_cc + t.string :cust_cc_exp + t.string :cust_addr + + t.timestamps + end + end +end diff --git a/db/migrate/20171017231112_create_products.rb b/db/migrate/20171017231112_create_products.rb new file mode 100644 index 0000000000..05f78884a1 --- /dev/null +++ b/db/migrate/20171017231112_create_products.rb @@ -0,0 +1,13 @@ +class CreateProducts < ActiveRecord::Migration[5.1] + def change + create_table :products do |t| + t.string :name + t.string :image_url + t.string :category + t.float :price + t.integer :quantity + t.text :description + t.timestamps + end + end +end diff --git a/db/migrate/20171017231524_create_reviews.rb b/db/migrate/20171017231524_create_reviews.rb new file mode 100644 index 0000000000..6952d5ad07 --- /dev/null +++ b/db/migrate/20171017231524_create_reviews.rb @@ -0,0 +1,10 @@ +class CreateReviews < ActiveRecord::Migration[5.1] + def change + create_table :reviews do |t| + t.integer :rating + t.string :description + + t.timestamps + end + end +end diff --git a/db/migrate/20171017231907_create_order_products.rb b/db/migrate/20171017231907_create_order_products.rb new file mode 100644 index 0000000000..948b400eec --- /dev/null +++ b/db/migrate/20171017231907_create_order_products.rb @@ -0,0 +1,9 @@ +class CreateOrderProducts < ActiveRecord::Migration[5.1] + def change + create_table :order_products do |t| + t.string :status + t.integer :quantity + t.timestamps + end + end +end diff --git a/db/migrate/20171018021532_add_foreign_keys.rb b/db/migrate/20171018021532_add_foreign_keys.rb new file mode 100644 index 0000000000..026fc5cd99 --- /dev/null +++ b/db/migrate/20171018021532_add_foreign_keys.rb @@ -0,0 +1,13 @@ +class AddForeignKeys < ActiveRecord::Migration[5.1] + def change + add_column :order_products, :order_id, :integer + add_column :order_products, :product_id, :integer + + add_column :products, :merchant_id, :integer + + add_column :orders, :merchant_id, :integer + + add_column :reviews, :merchant_id, :integer + add_column :reviews, :product_id, :integer + end +end diff --git a/db/migrate/20171018190422_add_oauth_data_to_merchants.rb b/db/migrate/20171018190422_add_oauth_data_to_merchants.rb new file mode 100644 index 0000000000..1243f9d815 --- /dev/null +++ b/db/migrate/20171018190422_add_oauth_data_to_merchants.rb @@ -0,0 +1,6 @@ +class AddOauthDataToMerchants < ActiveRecord::Migration[5.1] + def change + add_column :merchants, :oauth_provider, :string + add_column :merchants, :oauth_uid, :string + end +end diff --git a/db/migrate/20171018191131_require_oauth_parameters_for_merchants.rb b/db/migrate/20171018191131_require_oauth_parameters_for_merchants.rb new file mode 100644 index 0000000000..252d41c8b9 --- /dev/null +++ b/db/migrate/20171018191131_require_oauth_parameters_for_merchants.rb @@ -0,0 +1,6 @@ +class RequireOauthParametersForMerchants < ActiveRecord::Migration[5.1] + def change + change_column_null :merchants, :oauth_uid, false + change_column_null :merchants, :oauth_provider, false + end +end diff --git a/db/migrate/20171020221429_create_categories.rb b/db/migrate/20171020221429_create_categories.rb new file mode 100644 index 0000000000..5bef4913b8 --- /dev/null +++ b/db/migrate/20171020221429_create_categories.rb @@ -0,0 +1,9 @@ +class CreateCategories < ActiveRecord::Migration[5.1] + def change + create_table :categories do |t| + t.string :name + + t.timestamps + end + end +end diff --git a/db/migrate/20171020221607_create_categories_products.rb b/db/migrate/20171020221607_create_categories_products.rb new file mode 100644 index 0000000000..b87a74eb79 --- /dev/null +++ b/db/migrate/20171020221607_create_categories_products.rb @@ -0,0 +1,8 @@ +class CreateCategoriesProducts < ActiveRecord::Migration[5.1] + def change + create_table :categories_products do |t| + t.integer :category_id + t.integer :product_id + end + end +end diff --git a/db/migrate/20171020222435_remove_column_products_category_string.rb b/db/migrate/20171020222435_remove_column_products_category_string.rb new file mode 100644 index 0000000000..2410329855 --- /dev/null +++ b/db/migrate/20171020222435_remove_column_products_category_string.rb @@ -0,0 +1,5 @@ +class RemoveColumnProductsCategoryString < ActiveRecord::Migration[5.1] + def change + remove_column :products, :category, :string + end +end diff --git a/db/migrate/20171025214645_add_cvv_and_zip_code_to_orders.rb b/db/migrate/20171025214645_add_cvv_and_zip_code_to_orders.rb new file mode 100644 index 0000000000..6fe5142931 --- /dev/null +++ b/db/migrate/20171025214645_add_cvv_and_zip_code_to_orders.rb @@ -0,0 +1,6 @@ +class AddCvvAndZipCodeToOrders < ActiveRecord::Migration[5.1] + def change + add_column :orders, :cvv, :string + add_column :orders, :zip_code, :string + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000000..d0e5d70ec2 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,81 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 20171025214645) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "categories", force: :cascade do |t| + t.string "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "categories_products", force: :cascade do |t| + t.integer "category_id" + t.integer "product_id" + end + + create_table "merchants", force: :cascade do |t| + t.string "username" + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "oauth_provider", null: false + t.string "oauth_uid", null: false + end + + create_table "order_products", force: :cascade do |t| + t.string "status" + t.integer "quantity" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "order_id" + t.integer "product_id" + end + + create_table "orders", force: :cascade do |t| + t.string "cust_name" + t.string "status" + t.string "cust_email" + t.string "cust_cc" + t.string "cust_cc_exp" + t.string "cust_addr" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "merchant_id" + t.string "cvv" + t.string "zip_code" + end + + create_table "products", force: :cascade do |t| + t.string "name" + t.string "image_url" + t.float "price" + t.integer "quantity" + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "merchant_id" + end + + create_table "reviews", force: :cascade do |t| + t.integer "rating" + t.string "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "merchant_id" + t.integer "product_id" + end + +end diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 0000000000..92ef85cd58 --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,104 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) +# Character.create(name: 'Luke', movie: movies.first) + +merchants = Merchant.create([ + { username: 'Eagle', email: 'watchingyou@gmail.com', id: 1, oauth_provider: 'github', oauth_uid: '123456', created_at: Time.now, updated_at: Time.now }, + { username: 'Glimpie', email: 'mememe@hotmail.com', id: 2, oauth_provider: 'github', oauth_uid: '123457', created_at: Time.now, updated_at: Time.now }, + { username: 'Earthborn King', email: 'eking@something.com', id: 3, oauth_provider: 'github', oauth_uid: '123458', created_at: Time.now, updated_at: Time.now }, + { username: 'MagicBrian', email: 'brian@spider.com', id: 4, oauth_provider: 'github', oauth_uid: '123459', created_at: Time.now, updated_at: Time.now }, + { username: 'Taako', email: 'abra@cafu.com', id: 5, oauth_provider: 'github', oauth_uid: '123460', created_at: Time.now, updated_at: Time.now }, + { username: 'Forest Nymph', email: 'inatree@treepeople.com', id: 6, oauth_provider: 'github', oauth_uid: '123461', created_at: Time.now, updated_at: Time.now }, + { username: 'Bridge Troll', email: 'trolltoll@bridge.com', id: 7, oauth_provider: 'github', oauth_uid: '123462', created_at: Time.now, updated_at: Time.now }, + { username: 'Puck', email: 'sneakyelf@shakespeare.com', id: 8, oauth_provider: 'github', oauth_uid: '123463', created_at: Time.now, updated_at: Time.now } + ]) + +orders = Order.create([ + { cust_name: 'Elvy', status: 'paid', cust_email: 'elvii@gmail.com', cust_cc: '11111000000', cust_cc_exp: '02/18', cust_addr: 'Elves Land', id: 1, created_at: Time.now, updated_at: Time.now }, + { cust_name: 'Earthborn King', status: 'paid', cust_email: 'eking@something.com', cust_cc: '22222220000000', cust_cc_exp: '10/19', cust_addr: 'Magestic Castle VI', id: 2, created_at: Time.now, updated_at: Time.now }, + { cust_name: 'Eagle', status: 'completed', cust_email: 'watchingyou@gmail.com', cust_cc: '3333333000000', cust_cc_exp: '04/18', cust_addr: '2nd Rock ', id: 3, created_at: Time.now, updated_at: Time.now }, + { cust_name: 'Maeraly', status: 'completed', cust_email: 'maeraly@hotmail.com', cust_cc: '4444000000', cust_cc_exp: '02/21', cust_addr: '123 75th Line ', id: 4, created_at: Time.now, updated_at: Time.now }, + { cust_name: 'Sar Squatch', status: 'completed', cust_email: 'big@feet.com', cust_cc: '1234567890', cust_cc_exp: '06/19', cust_addr: 'The Woods', id: 5, created_at: Time.now, updated_at: Time.now }, + { cust_name: 'Ponyboy', status: 'paid', cust_email: 'big@feet.com', cust_cc: '1234552567890', cust_cc_exp: '06/19', cust_addr: 'Cartoon Network', id: 6, created_at: Time.now, updated_at: Time.now }, + { cust_name: 'Maleficent', status: 'completed', cust_email: 'veryevilfairy@hotmail.com', cust_cc: '1234567823423490', cust_cc_exp: '04/25', cust_addr: '12345 Perceforest', id: 7, created_at: Time.now, updated_at: Time.now }, + { cust_name: 'Witchy Witcherson', status: 'completed', cust_email: 'witch@witch.com', cust_cc: '1234567890234234', cust_cc_exp: '06/22', cust_addr: '294 Witch St, Witchville', id: 8, created_at: Time.now, updated_at: Time.now }, + { cust_name: 'Angry Waitress', status: 'completed', cust_email: 'dontmesswithbess@restaurant.com', cust_cc: '1234567890', cust_cc_exp: '10/90', cust_addr: 'The Restaurant', id: 9, created_at: Time.now, updated_at: Time.now }, + { cust_name: 'DND Master', status: 'paid', cust_email: 'dragon_person@email.com', cust_cc: '1234567890', cust_cc_exp: '10/20', cust_addr: 'Fantasy Land', id: 10, created_at: Time.now, updated_at: Time.now }, + { cust_name: 'Ialways Wearmask', status: 'completed', cust_email: 'standingoveryouwiththatcreepymask@yourhouse.com', cust_cc: '1234567890', cust_cc_exp: '10/20', cust_addr: 'Charbroiled Goodness St', id: 11, created_at: Time.now, updated_at: Time.now }, + { cust_name: 'Hermit Wizard', status: 'completed', cust_email: 'lookingforlove@lonelyville.com', cust_cc: '1234567890', cust_cc_exp: '10/20', cust_addr: 'The cave', id: 12, created_at: Time.now, updated_at: Time.now } + ]) + +categories = Category.create([ + {name: 'Magic Ingredients', id: 1}, + {name: 'Legendary Items', id: 2}, + {name: 'Mythical Creatures', id: 3}, + {name: 'Enchanted Food', id: 4}, + {name: 'Apparel', id: 5}, + {name: 'Curses', id: 6} + ]) + +products = Product.create([ + { name: 'Peaches of Immortality ', image_url: 'http://4.bp.blogspot.com/-5GQ1fBLNVr4/UAM6EnUpUSI/AAAAAAAAEyg/XKNYdq4P8x4/s1600/6a00e55119b5ec8834012877293a88970c-800wi.jpg', price: 3.25, quantity: 120, description: "Magical powers of immortality given from one bite", id: 1, created_at: Time.now, updated_at: Time.now, merchant_id: 4 }, + { name: 'That Ring', image_url: 'https://listverse.com/wp-content/uploads/2013/08/the-one-ring-e1375311691730.jpg', price: 350.00, quantity: 1, description: "The ring that gives you invisibility", id: 2, created_at: Time.now, updated_at: Time.now, merchant_id: 4 }, + {name: 'Dragon\'s Tooth', image_url: 'https://img1.etsystatic.com/100/0/9170080/il_340x270.831176717_5zaz.jpg', price: 9.99, quantity: 4, description: "Gives magical powers to it's owner.", id: 3, created_at: Time.now, updated_at: Time.now, merchant_id: 4 }, + { name: 'Helm of Darkness', image_url: 'http://cdn3.list25.com/wp-content/uploads/2017/04/HELMOFDARKNESSorsimilarmaybesubstituted-610x610.jpg', price: 49.99, quantity: 25, description: "Turn into a shadow with this magical helmet.", id: 4, created_at: Time.now, updated_at: Time.now , merchant_id: 5 }, + { name: 'Umbra Staff', image_url: 'https://image.freepik.com/free-photo/umbrella-closed_19-121570.jpg', price: 693.39, quantity: 10, description: "This is a supremely powerful magic wand, imbued with the soul of a great wizard. It's definitely not just an umbrella.", id: 5, created_at: Time.now, updated_at: Time.now, merchant_id: 6 }, + { name: 'My Little Pony (REAL)', image_url: 'https://lilyladewig.files.wordpress.com/2009/04/glitter-pony-2.jpg', price: 123.45, quantity: 100, description: "REAL PONY REAL MAGIC!! NOT FAKE", id: 6, created_at: Time.now, updated_at: Time.now, merchant_id: 7 }, + { name: 'Witch\'s Tears', image_url: 'https://i.ebayimg.com/images/g/BlsAAOSwWnFV84vC/s-l300.jpg', price: 21.21, quantity: 50, description: "You have to be really mean to a witch to make these.", id: 7, created_at: Time.now, updated_at: Time.now, merchant_id: 8 }, + {name: 'Golden Fleece', image_url: 'https://i.imgur.com/UMHsPJn.jpg', price: 5000.39 , quantity: 1, description: "Fresh Golden Fleece.", id: 8, created_at: Time.now, updated_at: Time.now, merchant_id: 7 }, + { name: 'Excalibur', image_url: 'https://i.imgur.com/2cGiWjQ.jpg', price: 50.01, quantity: 1, description: "Do you want to be the King of Camelot? Here is your chance. Also, people used to be a lot smaller than they are now.", id: 9, created_at: Time.now, updated_at: Time.now, merchant_id: 6 }, + { name: 'Ghost of Caesar in a Mason Jar', image_url: 'https://i.imgur.com/ys1of8Rr.jpg', price: 2145.96, quantity: 1, description: "There are so many uses for this ghost in a jar. Are you interested in learning Latin? Have someone you really need to scare? This ghost will haunt like none other. Just open the jar and your home, work, or back yard is haunted by Julius Caesar. You'll be saying 'E Tu, Brute' in no time.", id: 10, created_at: Time.now, updated_at: Time.now, merchant_id: 4 }, + { id: 11, name: 'Monkey\'s Paw', image_url: 'https://theluckycandle.com/wp-content/uploads/2014/07/Monkey-Paw-.jpg', price: 777.00, quantity: 60, description: 'The paw of a monkey, said to grant any wish in unfathomable ways.(Seller does not take any responsibility for the outcome of said wishes)', created_at: Time.now, updated_at: Time.now, merchant_id: 3 }, + { id: 12, name: 'Bloodied Parchments of Merlin', image_url: 'https://orig00.deviantart.net/e72e/f/2011/259/a/1/bloody_parchment_by_shinjite_samachan-d4a2dlf.jpg', price: 20, quantity: 300, description: 'Parchments said to come from the infamous wizard himself. Papers are bloodied to the point of illegibility.', created_at: Time.now, updated_at: Time.now, merchant_id: 4 }, + { id: 13, name: 'Golden Saddles of Mercury', image_url: 'https://i.pinimg.com/736x/3a/b2/b6/3ab2b6bc38cc66bbdad7c4cca2b02812.jpg', price: 104.67, quantity: 9, description: 'Sturdy leather saddle set for horse-riding enthusiasts. Recommended for winged steeds.', created_at: Time.now, updated_at: Time.now, merchant_id: 5 }, + { id: 14, name: 'Mermaid Fin', image_url: 'https://www.finfunmermaid.com/images/products/dragonfly-mermaid-tail_category.jpg', price: 35.00, quantity: 2017, description: 'Fins collected from mermaids who decided to give up their underwater life to be where the (land) people are.', created_at: Time.now, updated_at: Time.now, merchant_id: 6 } + ]) + +p = Product.find(1); p.categories << [Category.find(4), Category.find(1)]; p.save +p = Product.find(2); p.categories << Category.find(2); p.save +p = Product.find(3); p.categories << Category.find(1); p.save +p = Product.find(4); p.categories << Category.find(5); p.save +p = Product.find(5); p.categories << [Category.find(2), Category.find(4)]; p.save +p = Product.find(6); p.categories << Category.find(3); p.save +p = Product.find(7); p.categories << Category.find(1); p.save +p = Product.find(8); p.categories << Category.find(1); p.save +p = Product.find(9); p.categories << Category.find(2); p.save +p = Product.find(10); p.categories << Category.find(6); p.save +p = Product.find(11); p.categories << [Category.find(6), Category.find(1), Category.find(2)]; p.save +p = Product.find(12); p.categories << Category.find(1); p.save +p = Product.find(13); p.categories << Category.find(2); p.save +p = Product.find(14); p.categories << Category.find(3); p.save + +order_products = OrderProduct.create([ + { status: 'shipped', quantity: '1', order_id: 1, product_id: 13, created_at: Time.now, updated_at: Time.now, id: 1 }, + { status: 'shipped', quantity: '3', order_id: 2, product_id: 12, created_at: Time.now, updated_at: Time.now, id: 2 }, + { status: 'shipped', quantity: '1', order_id: 3, product_id: 11, created_at: Time.now, updated_at: Time.now, id: 3 }, + { status: 'shipped', quantity: '3', order_id: 4, product_id: 10, created_at: Time.now, updated_at: Time.now, id: 4 }, + { status: 'shipped', quantity: '1', order_id: 5, product_id: 8, created_at: Time.now, updated_at: Time.now, id: 5 }, + { status: 'shipped', quantity: '3', order_id: 6, product_id: 7, created_at: Time.now, updated_at: Time.now, id: 6 }, + { status: 'shipped', quantity: '1', order_id: 7, product_id: 9, created_at: Time.now, updated_at: Time.now, id: 7 }, + { status: 'shipped', quantity: '3', order_id: 8, product_id: 1, created_at: Time.now, updated_at: Time.now, id: 8 }, + { status: 'shipped', quantity: '1', order_id: 9, product_id: 2, created_at: Time.now, updated_at: Time.now, id: 9 }, + { status: 'shipped', quantity: '3', order_id: 10, product_id: 3, created_at: Time.now, updated_at: Time.now, id: 10 }, + { status: 'shipped', quantity: '1', order_id: 11, product_id: 4, created_at: Time.now, updated_at: Time.now, id: 11 }, + { status: 'shipped', quantity: '3', order_id: 12, product_id: 5, created_at: Time.now, updated_at: Time.now, id: 12 }, + { status: 'shipped', quantity: '1', order_id: 11, product_id: 6, created_at: Time.now, updated_at: Time.now, id: 13 }, + { status: 'shipped', quantity: '3', order_id: 10, product_id: 7, created_at: Time.now, updated_at: Time.now, id: 14 }, + { status: 'shipped', quantity: '1', order_id: 9, product_id: 8, created_at: Time.now, updated_at: Time.now, id: 15 }, + { status: 'shipped', quantity: '3', order_id: 8, product_id: 9, created_at: Time.now, updated_at: Time.now, id: 16 }, + { status: 'shipped', quantity: '1', order_id: 7, product_id: 14, created_at: Time.now, updated_at: Time.now, id: 17 } + ]) + +reviews = Review.create([ + { rating: 5, description: 'very nice and magic', product_id: 6, created_at: Time.now, updated_at: Time.now, id: 1 }, + { rating: 1, description: 'these are wizards tears, NOT witch\'s tears. seller should be ashamed', product_id: 7, created_at: Time.now, updated_at: Time.now, id: 2 } + ]) + +puts "Manually resetting PK sequence on each table" +ActiveRecord::Base.connection.tables.each do |t| + ActiveRecord::Base.connection.reset_pk_sequence!(t) +end diff --git a/lib/assets/.keep b/lib/assets/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/tasks/.keep b/lib/tasks/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/log/.keep b/log/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/package.json b/package.json new file mode 100644 index 0000000000..f874acf437 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "name": "betsy", + "private": true, + "dependencies": {} +} diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000000..2be3af26fc --- /dev/null +++ b/public/404.html @@ -0,0 +1,67 @@ + + + + The page you were looking for doesn't exist (404) + + + + + + +
+
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/public/422.html b/public/422.html new file mode 100644 index 0000000000..c08eac0d1d --- /dev/null +++ b/public/422.html @@ -0,0 +1,67 @@ + + + + The change you wanted was rejected (422) + + + + + + +
+
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/public/500.html b/public/500.html new file mode 100644 index 0000000000..78a030af22 --- /dev/null +++ b/public/500.html @@ -0,0 +1,66 @@ + + + + We're sorry, but something went wrong (500) + + + + + + +
+
+

We're sorry, but something went wrong.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000000..37b576a4a0 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb new file mode 100644 index 0000000000..d19212abd5 --- /dev/null +++ b/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/test/controllers/.keep b/test/controllers/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/controllers/categories_controller_test.rb b/test/controllers/categories_controller_test.rb new file mode 100644 index 0000000000..33855fd8b1 --- /dev/null +++ b/test/controllers/categories_controller_test.rb @@ -0,0 +1,50 @@ +require "test_helper" + +describe CategoriesController do + + let(:params) { + { "category_name" => "Test", + "product_id" => Product.first.id + } + } + + describe 'create' do + describe 'logged in users' do + before do + user = merchants(:ada) + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(user)) + get login_path(:github) + end + + it 'can be created by a logged-in user with valid data' do + before_count = Category.count + post categories_path, params: params + + must_respond_with :found + flash[:status].must_equal :success + Category.count.must_equal (before_count + 1) + end + + it 'cannot be created by a logged-in user with invalid data' do + params['category_name'] = "" + before_count = Category.count + post categories_path, params: params + + must_respond_with :found + flash[:status].must_equal :failure + Category.count.must_equal (before_count) + end + end + + describe 'guest users' do + it 'cannot be created by a guest user' do + before_count = Category.count + post categories_path, params: params + + must_respond_with :found + flash[:status].must_equal :failure + Category.count.must_equal (before_count) + end + end + end +end diff --git a/test/controllers/main_controller_test.rb b/test/controllers/main_controller_test.rb new file mode 100644 index 0000000000..af3db93f5c --- /dev/null +++ b/test/controllers/main_controller_test.rb @@ -0,0 +1,58 @@ +require "test_helper" + +describe MainController do + let(:wand) { products(:wand) } + + it 'can successfully access the index page' do + get root_path + + must_respond_with :success + end + + it 'can successfully access the shopping cart page' do + get shopping_cart_path + + must_respond_with :success + end + + describe '#add_to_cart' do + it 'adds items to cart' do + params = { + "quantity" => "4", + "id" => wand.id + } + + post add_to_cart_path(wand.id), params: params + + must_respond_with :found + flash[:status].must_equal :success + session[:cart].length.must_equal 1 + end + + it 'returns not_found for DNE product' do + bad_id = Product.last.id + 1 + params = { + "quantity" => "4", + "id" => bad_id + } + + post add_to_cart_path(bad_id), params: params + + must_respond_with :not_found + session[:cart].length.must_equal 0 + end + + it 'wont work if no quantity is passed' do + params = { + "id" => wand.id + } + + post add_to_cart_path(wand.id), params: params + + must_respond_with :found + flash[:status].must_equal :failure + + session[:cart].length.must_equal 0 + end + end +end diff --git a/test/controllers/merchants_controller_test.rb b/test/controllers/merchants_controller_test.rb new file mode 100644 index 0000000000..2817f0fbee --- /dev/null +++ b/test/controllers/merchants_controller_test.rb @@ -0,0 +1,295 @@ +require "test_helper" + +describe MerchantsController do + + describe "show" do + it "succeeds for an extant merchant" do + get merchant_path(Merchant.first) + must_respond_with :success + end + + it "renders 404 not_found for a bogus merchant" do + bogus_merchant_id = Merchant.last.id + 1 + get merchant_path(bogus_merchant_id) + must_respond_with :not_found + end + end + + describe "login" do + + it "logs in an existing merchant and redirects to the root route" do + start_count = Merchant.all.count + + merchant = merchants(:grace) + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(merchant)) + + get login_path(:github) + + must_redirect_to root_path + + session[:merchant][:id].must_equal merchant.id + + Merchant.all.count.must_equal start_count + end + + it "creates new user with new info" do + + start_count = Merchant.all.count + new_merchant = Merchant.new(oauth_provider: 'github',oauth_uid: "adlgkcl",email: 'mail@mail.com', username: 'Mr. New') + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(new_merchant)) + get login_path(:github) + must_redirect_to root_path + Merchant.count.must_equal start_count + 1 + session[:merchant][:id].must_equal Merchant.last.id + flash[:result_text].must_equal "Successfully created new merchant " + flash[:status].must_equal :success + + end + + it "generates failure with no info" do + start_count = Merchant.all.count + OmniAuth.config.mock_auth[:github] = nil + get login_path(:github) + Merchant.count.must_equal start_count + flash[:result_text].must_equal "Not logged in" + flash[:status].must_equal :failure + flash[:messages].must_include :email + flash[:messages].must_include :username + must_redirect_to root_path + end + + end + + describe "logout" do + it "logs merchants out" do + get logout_path + session[:merchant].must_equal nil + must_redirect_to root_path + end + end + + describe "create" do + it "works" do + merchant = { + merchant: { + oauth_provider: "github", + oauth_uid: "99999", + email: "somebody@somesite.com", + username: "Somebody" + } + } + Merchant.new(merchant[:merchant]).must_be :valid? + start_count = Merchant.count + post merchants_path, params: merchant + flash[:status].must_equal :success + flash[:message].must_include "Successfully created merchant" + must_respond_with :redirect + must_redirect_to root_path + Merchant.count.must_equal start_count + 1 + end + + + it "re-renders form when the merchant data is invalid" do + merchant = { + merchant: { + oauth_provider: "github", + oauth_uid: "", + email: "somebodyelse@somesite.com", + username: "Somebody_else" + } + } + Merchant.new(merchant[:merchant]).wont_be :valid? + start_count = Merchant.count + + post merchants_path, params: merchant + + must_respond_with :bad_request + Merchant.count.must_equal start_count + end + end + + describe "edit" do + it "succeeds for an extant merchant ID" do + get edit_merchant_path(Merchant.first) + must_respond_with :success + end + it "renders 404 not_found for a bogus merchant ID" do + bogus_merchant_id = Merchant.last.id + 1 + get edit_merchant_path(bogus_merchant_id) + must_respond_with :not_found + end + end + describe "destroy" do + it "removes the merchant and goes to the root path" do + first_count = Merchant.all.count + delete_merchant = Merchant.first + delete merchant_path(delete_merchant) + must_respond_with :redirect + must_redirect_to root_path + Merchant.all.count.must_equal first_count - 1 + + end + end + describe "update" do + + it "returns success if the merchant ID is valid and the change is valid" do + merchant = Merchant.first + merchant_data = { + merchant: { + username: "changed username", + email: merchant.email + } + } + merchant.update_attributes(merchant_data[:merchant]) + merchant.must_be :valid?, "Test is invalid because the provided data will produce an invalid merchant" + + patch merchant_path(merchant), params: merchant_data + + must_respond_with :redirect + must_redirect_to merchant_path(merchant) + + # Check that the change went through + merchant.reload + merchant.username.must_equal merchant_data[:merchant][:username] + end + # + it "returns not_found if the merchant ID is invalid" do + invalid_merchant_id = Merchant.last.id + 1 + merchant_data = { + merchant: { + username: "Changed username", + merchant_id: Merchant.first.id + } + } + + patch merchant_path(invalid_merchant_id), params: merchant_data + + must_respond_with :not_found + end + # + it "returns bad_request if the change is invalid" do + merchant = Merchant.first + invalid_merchant_data = { + merchant: { + email: "" + } + } + # Check that the update is actually invalid + merchant.update_attributes(invalid_merchant_data[:merchant]) + merchant.wont_be :valid? + patch merchant_path(merchant), params: invalid_merchant_data + + must_respond_with :bad_request + + merchant.reload + merchant.username.wont_equal invalid_merchant_data[:merchant][:username] + end + end + + describe "user pages" do + describe "logged in" do + before do + user = merchants(:ada) + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(user)) + get login_path(:github) + end + + it 'logged in user can access summary' do + get self_summary_path + + must_respond_with :success + end + + it 'logged in user can access pending' do + get self_pending_path + + must_respond_with :success + end + + it 'logged in user can access completed' do + get self_completed_path + + must_respond_with :success + end + + it 'logged in user can access revenue' do + get self_summary_path + + must_respond_with :success + end + + it 'logged in user can access inventory' do + get self_summary_path + + must_respond_with :success + end + + describe 'mark_shipped' do + it 'changes the status of an existing product owned by the user' do + op_id = order_products(:order_products).id + get mark_shipped_path(op_id) + + must_respond_with :found + flash[:status].must_equal :success + OrderProduct.find(op_id).status.must_equal "shipped" + end + + it 'returns not_found if product does not exist' do + get mark_shipped_path(OrderProduct.last.id + 1) + + must_respond_with :not_found + end + + it 'redirects without changing if product does not belong to user' do + op_id = order_products(:other_order_products).id + get mark_shipped_path(op_id) + + must_respond_with :found + flash[:status].must_equal :failure + OrderProduct.find(op_id).status.wont_equal "shipped" + end + end + end + + describe "guests" do + it 'guest user cannot access summary' do + get self_summary_path + + must_respond_with :found + end + + it 'guest user cannot access pending' do + get self_pending_path + + must_respond_with :found + end + + it 'guest user cannot access completed' do + get self_completed_path + + must_respond_with :found + end + + it 'guest user cannot access revenue' do + get self_summary_path + + must_respond_with :found + end + + it 'guest user cannot access inventory' do + get self_summary_path + + must_respond_with :found + end + + it 'guest user cannot access mark_shipped' do + op_id = order_products(:order_products).id + get mark_shipped_path(op_id) + + must_respond_with :found + flash[:status].must_equal :failure + OrderProduct.find(op_id).status.wont_equal "shipped" + end + end + end +end diff --git a/test/controllers/order_products_controller_test.rb b/test/controllers/order_products_controller_test.rb new file mode 100644 index 0000000000..6553c37621 --- /dev/null +++ b/test/controllers/order_products_controller_test.rb @@ -0,0 +1,75 @@ +require "test_helper" + +describe OrderProductsController do + describe "index" do + it "returns success for the order products of a specific merchant if given a merchant" do + merchant = :ada + get merchant_sold_index_path(merchant) + must_respond_with :success + end + end + + describe "update" do + it "returns success if order product exists and changes are valid" do + order_product = OrderProduct.first + changes = { + order_product: { + quantity: 2 + } + } + order_product.update_attributes(changes[:order_product]) + order_product.must_be :valid? + + patch order_product_path(order_product), params: changes + must_respond_with :redirect + must_redirect_to shopping_cart_path + + order_product.reload + order_product.quantity.must_equal changes[:order_product][:quantity] + end + + it "returns failure if order product exists and changes are invalid" do + order_product = OrderProduct.first + changes = { + order_product: { + quantity: 900000 + } + } + patch order_product_path(order_product), params: changes + order_product.quantity.wont_equal 900000 + end + + it "returns not_found if order product does not exist" do + order_product = OrderProduct.first + changes = { + order_product: { + quantity: 2 + } + } + order_product.update_attributes(changes[:order_product]) + order_product.must_be :valid? + order_product.destroy + + patch order_product_path(order_product), params: changes + must_respond_with :not_found + end + end + + describe "destroy" do + it "returns success and destroys if order product exists" do + order_product = OrderProduct.first + order_product.must_be :valid? + delete order_product_path(order_product) + must_respond_with :redirect + must_redirect_to shopping_cart_path + end + + it "returns not_found if order product does not exist" do + order_product = OrderProduct.first + order_product.must_be :valid? + delete order_product_path(order_product) + delete order_product_path(order_product) + must_respond_with :not_found + end + end +end diff --git a/test/controllers/orders_controller_test.rb b/test/controllers/orders_controller_test.rb new file mode 100644 index 0000000000..bedcb6c29e --- /dev/null +++ b/test/controllers/orders_controller_test.rb @@ -0,0 +1,171 @@ +require "test_helper" + +describe OrdersController do + describe "index" do + it "returns success for the orders of a specific merchant if given a merchant" do + merchant = :ada + login_test_user + get merchant_orders_path(merchant) + must_respond_with :success + end + + it "will not show page to guest users" do + merchant = :ada + get merchant_orders_path(merchant) + must_redirect_to root_path + end + end + + describe "new" do + it "returns success for a new orders" do + get new_order_path + must_respond_with :success + end + end + + describe "create" do + it "adds order to the database with at least 1 order product and redirects when the data is valid" do + login_test_user + cart_params = { + "quantity" => 4, + "id" => products(:wand).id + } + post add_to_cart_path(products(:wand).id), params: cart_params + + order = { + order: { + cust_name: "Mermaid", + merchant_id: 21, + cust_cc: 12345, + cust_cc_exp: "11/22", + cust_addr: "Sea World", + cust_email: "forkhair@mermaid.com", + cvv: 123, + zip_code: 32444, + status: "complete", + created_at: Time.now, + updated_at: Time.now + } + } + good_order = Order.new(order[:order]) + good_order.must_be :valid? + start_count = Order.count + + post orders_path, params: order + + must_respond_with :redirect + must_redirect_to confirmation_path(Order.last.id) + Order.count.must_equal start_count + 1 + flash[:status].must_equal :success + flash[:message].must_include "Successfully created order" + end + + it "re-renders form when the order data is invalid" do + order = { + order: { + cust_name: "Mermaid", + merchant_id: 21, + cust_cc: 12345, + cust_cc_exp: "11/22", + cust_addr: "Sea World", + cvv: 123, + zip_code: 32444, + cust_email: "forkhair@mermaid.com", + status: "" + } + } + Order.new(order[:order]).wont_be :valid? + start_count = Order.count + + post orders_path, params: order + + must_respond_with :bad_request + Order.count.must_equal start_count + end + end + + describe "show" do + it "returns success with valid id when logged in" do + login_test_user + order_id = Order.first.id + get order_path(order_id) + must_respond_with :success + end + + it "returns not_found with invalid id when logged in" do + login_test_user + invalid_id = Order.last.id + 1 + get order_path(invalid_id) + must_respond_with :not_found + end + end + + describe "edit" do + it "returns success when the order belongs to the merchant" do + login_test_user + cart_params = { + "quantity" => 4, + "id" => products(:wand).id + } + post add_to_cart_path(products(:wand).id), params: cart_params + order = { + order: { + cust_name: "Mermaid", + merchant_id: merchants(:ada).id, + cust_cc: 12345, + cust_cc_exp: "11/22", + cust_addr: "Sea World", + cust_email: "forkhair@mermaid.com", + cvv: 123, + zip_code: 32444, + status: "complete", + created_at: Time.now, + updated_at: Time.now + } + } + order = Order.create!(order[:order]) + order_id = Order.last.id + get edit_order_path(order_id) + must_respond_with :success + end + it "returns failure when the order does not belong to the merchant" do + end + end + + describe "update" do + it "returns success if at least 1 order product exists and changes are valid" do + login_test_user + order = Order.first + changes = { + order: { + cust_name: "Wienerschnitzel" + } + } + order.update_attributes(changes[:order]) + order.must_be :valid? + + patch order_path(order), params: changes + + must_respond_with :redirect + must_redirect_to order_path(order.id) + order.reload + order.cust_name.must_equal changes[:order][:cust_name] + end + + it "returns failure if at least 1 order product exists and changes are invalid" do + login_test_user + order = Order.first + changes = { + order: { + cust_name: "" + } + } + order.update_attributes(changes[:order]) + order.wont_be :valid? + + patch order_path(order), params: changes + + must_respond_with :bad_request + end + end +end diff --git a/test/controllers/products_controller_test.rb b/test/controllers/products_controller_test.rb new file mode 100644 index 0000000000..69a2d84725 --- /dev/null +++ b/test/controllers/products_controller_test.rb @@ -0,0 +1,304 @@ +require "test_helper" + +describe ProductsController do + let(:prod) { products(:mermaid_fin) } + let(:good_params) { { product: { "name"=>"New Test Item", "price"=>"11.00", "quantity"=>"1", "description"=>"This is a test" } } } + let(:bad_params) { { product: { "name"=>"" } } } + let(:tmi_params) { { product: { "name"=>"New Test Item", "price"=>"11.00", "quantity"=>"1", "description"=>"This is a test", "tmi"=>"uh oh", "id"=>1 } } } + let(:new_params) { { product: { "name"=>"Updated Name" } } } + let(:owned_product) { owned_product = Product.find_by(merchant_id: merchants(:ada).id) } + let(:not_owned_product) { not_owned_product = Product.find_by(merchant_id: merchants(:grace).id) } + + before do + @before_count = Product.count + end + + describe 'for logged in users' do + before do + user = merchants(:ada) + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(user)) + get login_path(:github) + end + + describe 'index' do + it 'can successfully access index of products' do + get products_path + + must_respond_with :success + end + + it 'can successfully access nested index of products with valid id' do + get category_products_path(Category.first.id) + + controller.instance_variables.must_include :@cat + must_respond_with :success + end + + it 'returns 404 for invalid category ID in nested route' do + get products_path, params: { 'category_id' => (Category.last.id + 1) } + + must_respond_with :not_found + end + end + + describe 'show' do + it 'can successfully access show product with valid ID' do + get product_path(prod.id) + + must_respond_with :success + end + + it 'returns 404 with invalid ID' do + get product_path( Product.last.id + 1 ) + + must_respond_with :not_found + end + end + + it 'can successfully access new product' do + get new_product_path + + must_respond_with :success + end + + describe 'create' do + it 'can successfully create valid product' do + post products_path, params: good_params + + must_respond_with :found + flash[:status].must_equal :success + Product.count.must_equal (@before_count + 1) + end + + it 'will not create new product with invalid data' do + post products_path, params: bad_params + + must_respond_with :bad_request + flash[:status].must_equal :failure + Product.count.must_equal @before_count + end + + it 'uses strong params' do + post products_path, params: tmi_params + + must_respond_with :found + flash[:status].must_equal :success + Product.last.id.wont_equal 1 + Product.count.must_equal (@before_count + 1) + end + end + + describe 'edit' do + it 'can successfully access edit for own product' do + get edit_product_path(owned_product.id) + + must_respond_with :success + end + + it 'CANNOT successfully access edit for not found product (returns not found)' do + get edit_product_path(Product.last.id + 1) + + must_respond_with :not_found + end + + it 'CANNOT successfully edit other users products' do + get edit_product_path(not_owned_product.id) + + must_respond_with :found + flash[:status].must_equal :failure + end + end + + describe 'update' do + it 'can successfully update own product & redirect to product show page with valid data' do + patch product_path(owned_product.id), params: new_params, headers: { "HTTP_REFERER" => edit_product_path(owned_product.id) } + id = owned_product.id + + Product.find(id).name.must_equal "Updated Name" + flash[:status].must_equal :success + must_redirect_to product_path + must_respond_with :found + end + + it 'redirects to inventory path if coming from inventory management page' do + patch product_path(owned_product.id), params: new_params, headers: { "HTTP_REFERER" => self_inventory_path } + id = owned_product.id + + Product.find(id).name.must_equal "Updated Name" + flash[:status].must_equal :success + must_redirect_to self_inventory_path + must_respond_with :found + end + + it 'CANNOT successfully update own product with invalid data' do + patch product_path(owned_product.id), params: bad_params + + must_respond_with :bad_request + flash[:status].must_equal :failure + end + + it 'CANNOT successfully update other users product' do + patch product_path(not_owned_product.id), params: new_params + + must_respond_with :found + flash[:status].must_equal :failure + end + end + + describe 'destroy' do + it 'can successfully destroy own product' do + delete product_path(owned_product.id) + + must_respond_with :found + flash[:status].must_equal :success + Product.count.must_equal (@before_count - 1) + end + + it 'CANNOT destroy other users products' do + delete product_path(not_owned_product.id) + + must_respond_with :found + flash[:status].must_equal :failure + Product.count.must_equal @before_count + end + end + + describe 'categories' do + it 'can access page for own products' do + get add_categories_path(owned_product.id) + + must_respond_with :success + end + + it 'CANNOT access page for other users products' do + get add_categories_path(not_owned_product.id) + + must_respond_with :found + flash[:status].must_equal :failure + end + end + + describe 'add_categories' do + it 'can successfully add valid categories to own products' do + cat_id = categories(:magic).id + test_params = { + id: owned_product.id, + "category_Magic Items" => cat_id, + } + before_count = owned_product.categories.count + + post add_categories_path(owned_product.id), params: test_params + + must_respond_with :found + flash[:status].must_equal :success + owned_product.categories.count.must_equal (before_count + 1) + end + + it 'CANNOT add invalid categories to own products' do + cat_id = categories(:magic).id + test_params = { + id: owned_product.id, + "category_bad_id" => (Category.last.id + 1), + } + + before_count = owned_product.categories.count + post add_categories_path(owned_product.id), params: test_params + + must_respond_with :found + flash[:status].must_equal :failure + owned_product.categories.count.must_equal before_count + end + + it 'CANNOT successfully add categories to other users products' do + test_params = { "product_id"=>"2", + "category_Magic Items"=>"5", + id: :id + } + + post add_categories_path(not_owned_product.id), params: test_params + + must_respond_with :found + flash[:status].must_equal :failure + end + end + end + + describe 'for guest users' do + describe 'index' do + it 'can successfully access index of products' do + get products_path + + must_respond_with :success + end + + it 'can successfully access nested index of products with valid id' do + get category_products_path(Category.first.id) + + controller.instance_variables.must_include :@cat + must_respond_with :success + end + + it 'returns 404 for invalid category ID in nested route' do + get products_path, params: { 'category_id' => (Category.last.id + 1) } + + must_respond_with :not_found + end + end + + describe 'show' do + it 'can successfully access show product with valid ID' do + get product_path(prod.id) + + must_respond_with :success + end + + it 'returns 404 with invalid ID' do + get product_path( Product.last.id + 1 ) + + must_respond_with :not_found + end + end + + it 'CANNOT access new product' do + get new_product_path(prod.id) + + flash[:status].must_equal :failure + must_respond_with :found + end + + it 'CANNOT create valid product' do + post products_path(prod.id), params: good_params + + flash[:status].must_equal :failure + must_respond_with :found + end + + it 'CANNOT access edit for product' do + get edit_product_path(prod.id) + + flash[:status].must_equal :failure + must_respond_with :found + end + + it 'CANNOT update product' do + patch product_path(prod.id), params: good_params + + flash[:status].must_equal :failure + must_respond_with :found + end + + it 'CANNOT destroy other users products' do + delete product_path(prod.id) + + flash[:status].must_equal :failure + must_respond_with :found + end + + it 'CANNOT access add categories page' do + get add_categories_path(owned_product.id) + + must_respond_with :found + flash[:status].must_equal :failure + end + end +end diff --git a/test/controllers/reviews_controller_test.rb b/test/controllers/reviews_controller_test.rb new file mode 100644 index 0000000000..85350a0c32 --- /dev/null +++ b/test/controllers/reviews_controller_test.rb @@ -0,0 +1,222 @@ +require "test_helper" + +describe ReviewsController do + let(:review) { reviews(:review) } + let(:mermaid_fin) { products(:mermaid_fin) } + let(:wand) { products(:wand) } + +######### Leaving for future, if we rethink and decide to add later + # describe "index" do + # it "returns success status for all reviews" do + # get reviews_path + # must_respond_with :success + # end + # + # it "works when there are no reviews" do + # Review.destroy_all + # get reviews_path + # must_respond_with :success + # end + # end + + # describe "show" do + # it "succeeds for a review that exists" do + # get review_path(reviews(:review)) + # must_respond_with :success + # end + # + # it "returns 404 not_found for a review that does not exist" do + # get review_path(123) + # must_respond_with :not_found + # end + # end + + describe "new" do + + it " should not work if product doesn't exist" do + bad_product = Product.last.id + 1 + get new_product_review_path(bad_product) + + must_respond_with :not_found + end + + it " should work for a non-user" do + logout_test_user + get new_product_review_path(mermaid_fin) + must_respond_with :success + end + + it " should not work if product belongs to merchant " do + # Arrange + login_test_user + + # Act + get new_product_review_path(mermaid_fin) + + # Assert + must_respond_with :redirect + must_redirect_to product_path(mermaid_fin) + end + end + + describe "create" do + + # We are already checking "new" method for owenership + # it "should return failure if product belongs to merchant " do + # # arrange: login 'ada' user + # login_test_user + # review_data = { + # review: { + # rating: 5, + # product_id: mermaid_fin.id + # } + # } + # start_review_count = Review.count + # # binding.pry + # # act: review (Mermaid Fin) + # post reviews_path, params: review_data + # + # # assert: expect failure. + # must_respond_with :redirect + # must_redirect_to product_path(mermaid_fin) + # Review.count.must_equal start_review_count + # end + + it "adds the review to the product and redirects when the review data is valid" do + # Arrange + review_data = { + review: { + rating: 5, + product_id: mermaid_fin.id + } + } + start_review_count = Review.count + # Act + post reviews_path, params: review_data + # Assert + must_redirect_to product_path(mermaid_fin) + Review.count.must_equal start_review_count + 1 + end + + + it "sends bad_request if review data is invalid" do + # Arrange + invalid_review_data = { + review: { + #No rating + product_id: mermaid_fin.id + } + } + # Double checking the data is truly invalid + Review.new(invalid_review_data[:review]).wont_be :valid? + start_review_count = Review.count + + # Act + post reviews_path, params: invalid_review_data + + # Assert + must_respond_with :bad_request + Review.count.must_equal start_review_count + end + end + + describe "edit" do + + before do + login_test_user + end + + it "will return failure if merchant owns the product" do + # login_test_user + + get edit_review_path(review) + + must_redirect_to product_path(mermaid_fin) + end + + it "succeeds for an exact review ID" do + + review_id = Review.first.id + get edit_review_path(review.id) + must_respond_with :found + end + + it "renders 404 not_found for a bogus review ID" do + bogus_review_id = Review.last.id + 1 + get edit_review_path(bogus_review_id) + must_respond_with :not_found + end + end + + describe "update" do + + it "will successfully update review" do + review_data = { + review: { + rating: 3, + product: mermaid_fin.attributes + } + } + + patch review_path(review.id), params: review_data + + must_respond_with :redirect + must_redirect_to product_path(mermaid_fin) + + Review.find(review.id).rating.must_equal 3 + end + + it "will return not found, if review doesn't exist" do + bad_id = Review.last.id + 1 + review_data = { + review:{ + rating: 3, + description: "It is a good product " + } + } + patch review_path(bad_id), params: review_data + must_respond_with :not_found + end + + it "will not let you invalidate review" do + review_data = { + review: { + rating: 'shdfgsjh', + product: mermaid_fin.attributes + } + } + + patch review_path(review.id), params: review_data + + must_respond_with :bad_request + end + end + + describe "destroy" do + + before do + login_test_user + end + + it "will successfully destroy review" do + + start_review_count = Review.count + + delete review_path(reviews(:review4)) + # binding.pry + must_redirect_to product_path(wand.id) + + flash[:status].must_equal :success + Review.count.must_equal start_review_count - 1 + end + + it "will not delete review if merchant owns the product" do + # login_test_user + start_review_count = Review.count + delete review_path(review) + + must_redirect_to product_path(mermaid_fin.id) + Review.count.must_equal start_review_count + end + end +end diff --git a/test/fixtures/.keep b/test/fixtures/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/categories.yml b/test/fixtures/categories.yml new file mode 100644 index 0000000000..deee5548f9 --- /dev/null +++ b/test/fixtures/categories.yml @@ -0,0 +1,7 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +magic: + name: Magic Items + +food: + name: Food diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/merchants.yml b/test/fixtures/merchants.yml new file mode 100644 index 0000000000..fee9544e95 --- /dev/null +++ b/test/fixtures/merchants.yml @@ -0,0 +1,18 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# + +ada: + oauth_provider: github + oauth_uid: 12345 + email: ada@adadevelopersacademy.org + username: countess_ada + +grace: + oauth_provider: github + oauth_uid: 13371337 + email: grace@hooper.net + username: graceful_hoops diff --git a/test/fixtures/order_products.yml b/test/fixtures/order_products.yml new file mode 100644 index 0000000000..0ca4de9018 --- /dev/null +++ b/test/fixtures/order_products.yml @@ -0,0 +1,31 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + quantity: 2 + product: mermaid_fin + order: order + status: pending + +two: + quantity: 3 + product: mermaid_fin + order: order + status: shipped + +order_products: + quantity: 2 + product: mermaid_fin + order: order + status: pending + +other_order_products: + quantity: 3 + product: wand + order: order + status: pending + +guest_op: + quantity: 5 + product: mermaid_fin + order: guest_order + status: pending diff --git a/test/fixtures/orders.yml b/test/fixtures/orders.yml new file mode 100644 index 0000000000..8b21c8ea8c --- /dev/null +++ b/test/fixtures/orders.yml @@ -0,0 +1,33 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +order: + cust_name: Mermaid + merchant_id: :ada + cust_cc: 12345 + cust_cc_exp: 11/22 + cvv: 111 + zip_code: 32444 + cust_addr: Sea World + cust_email: forkhair@mermaid.com + status: pending + +other_order: + cust_name: Demon + merchant_id: :grace + cust_cc: 5432314 + cust_cc_exp: 66/66 + cvv: 111 + zip_code: 32444 + cust_addr: Fire World + cust_email: flameshair@mermaid.com + status: pending + +guest_order: + cust_name: Gaston + cust_cc: 1234098756 + cust_cc_exp: 12/48 + cvv: 324 + zip_code: 56123 + cust_addr: A small provincial town + cust_email: gaston@beautybeast.com + status: pending diff --git a/test/fixtures/products.yml b/test/fixtures/products.yml new file mode 100644 index 0000000000..b0c666f4f2 --- /dev/null +++ b/test/fixtures/products.yml @@ -0,0 +1,17 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# +mermaid_fin: + name: Mermaid Fin + price: 3.45 + quantity: 30 + merchant: ada + +wand: + name: Magic Wand + price: 10.14 + quantity: 10 + merchant: grace diff --git a/test/fixtures/reviews.yml b/test/fixtures/reviews.yml new file mode 100644 index 0000000000..479d5e3d4d --- /dev/null +++ b/test/fixtures/reviews.yml @@ -0,0 +1,25 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below + +review: + rating: 4 + merchant: ada + product: mermaid_fin + +review2: + rating: 3 + merchant: ada + product: mermaid_fin + +review3: + rating: 4 + merchant: grace + product: wand + +review4: + rating: 4 + merchant: ada + product: wand diff --git a/test/helpers/.keep b/test/helpers/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/helpers/helpers_test.rb b/test/helpers/helpers_test.rb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/integration/.keep b/test/integration/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/mailers/.keep b/test/mailers/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/models/.keep b/test/models/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/models/category_test.rb b/test/models/category_test.rb new file mode 100644 index 0000000000..3d54153e03 --- /dev/null +++ b/test/models/category_test.rb @@ -0,0 +1,42 @@ +require "test_helper" + +describe Category do + let(:c) { Category.new(name: "Test") } + + describe 'validations' do + it 'can be valid' do + c.must_be :valid? + end + + describe 'name' do + it 'must be present' do + c.name.clear + + c.wont_be :valid? + c.errors.messages.must_include :name + end + + it 'must be unique' do + d = c.dup + d.save + + c.wont_be :valid? + c.errors.messages.must_include :name + end + + it 'tests uniqueness - case sensitive' do + d = Category.new(name: "TEST") + d.save + + c.wont_be :valid? + c.errors.messages.must_include :name + end + end + end + + describe 'relationships' do + it 'responds to products' do + c.must_respond_to :products + end + end +end diff --git a/test/models/merchant_test.rb b/test/models/merchant_test.rb new file mode 100644 index 0000000000..f22ff95b31 --- /dev/null +++ b/test/models/merchant_test.rb @@ -0,0 +1,146 @@ +require "test_helper" + +describe Merchant do + let(:merchant) { Merchant.new( username: 'testman', + email: 'test@test.com', + oauth_provider: 'github', + oauth_uid: '1234567') + } + + # it "must be valid" do + # value(merchant).must_be :valid? + # end + + describe 'validations' do + it 'can be created with valid data' do + merchant.must_be :valid? + end + + describe 'username' do + it 'requires username' do + merchant.username = nil + merchant.email = 'test@test.com' + merchant.oauth_provider = 'github' + merchant.oauth_uid = '1234567' + merchant.save + merchant.errors.messages.must_include :username + merchant.wont_be :valid? + end + it "has a unique username" do + merchant.username = "countess_ada" + merchant.wont_be :valid? + merchant.errors.messages.must_include :username + end + end + + describe "username email validations" do + describe "tests for format of email" do + + it "will reject a bad email" do + merchant.email = "I'm an email" + merchant.wont_be :valid? + merchant.errors.messages.must_include :email + end + end + + describe "tests for presence of email" do + it "will reject missing email" do + merchant.email = nil + merchant.wont_be :valid? + merchant.errors.messages.must_include :email + end + end + end + + describe "oauth validations" do + it "will reject not unique oauth_uid" do + merchant.oauth_uid = "12345" + merchant.wont_be :valid? + merchant.errors.messages.must_include :oauth_uid + end + end + + describe "oauth_provider" do + it "will be rejected if oauth_provider not present" do + merchant.oauth_provider = nil + merchant.wont_be :valid? + merchant.errors.messages.must_include :oauth_provider + end + end + end + + describe 'relationships' do + it "has a list of order_products" do + mermaid_fin = merchants(:ada) + mermaid_fin.must_respond_to :products + mermaid_fin.products.each do |product| + product.must_be_kind_of Product + end + end + + it "has a list of reviews" do + review = merchants(:ada) + review.must_respond_to :reviews + review.reviews.each do |review| + review.must_be_kind_of Review + end + end + + it "has a list of orders" do + merchant = merchants(:ada) + merchant.must_respond_to :orders + merchant.orders.each do |order| + order.must_be_kind_of Order + end + end + + it "has a list of order products" do + order_product = merchants(:ada) + order_product.must_respond_to :order_products + end + end + describe "self.by_auth_hash(auth_hash)" do + it "has an instance of merchant" do + auth_hash = { + 'provider' => "github", + 'uid' => "12345", + 'info' => {'email' => "somebody@somesite.com", + 'nickname' => "Somebody"} + } + merchant_auth = Merchant.by_auth_hash auth_hash + merchant_auth.must_be_kind_of Merchant + # merchant_auth.must_respond_to + # binding.pry + end + + it "responds to the proper keys" do + auth_hash = { + 'provider' => "github", + 'uid' => "12345", + 'info' => {'email' => "somebody@somesite.com", + 'nickname' => "Somebody"} + } + merchant_auth = Merchant.by_auth_hash auth_hash + merchant_auth.must_respond_to :oauth_provider + merchant_auth.must_respond_to :oauth_uid + merchant_auth.must_respond_to :email + merchant_auth.must_respond_to :username + # binding.pry + end + + it "has the correct values for merchant from auth_hash" do + auth_hash = { + 'provider' => "github", + 'uid' => "12345", + 'info' => {'email' => "somebody@somesite.com", + 'nickname' => "Somebody"} + } + merchant_auth = Merchant.by_auth_hash auth_hash + merchant_auth.oauth_provider.must_equal "github" + merchant_auth.oauth_uid.must_equal "12345" + merchant_auth.email.must_equal "somebody@somesite.com" + merchant_auth.username.must_equal "Somebody" + end + + end + end diff --git a/test/models/order_product_test.rb b/test/models/order_product_test.rb new file mode 100644 index 0000000000..4a254a568e --- /dev/null +++ b/test/models/order_product_test.rb @@ -0,0 +1,40 @@ +require "test_helper" + +describe OrderProduct do + let(:order_product) { order_products(:one)} + + describe "validations" do + describe "quantity" do + it "should return false without a quantity" do + order_product.valid?.must_equal true + order_product.quantity = nil + order_product.valid?.must_equal false + end + + it "should return false if quantity is not greater than 0 " do + order_product.quantity = 0 + order_product.valid?.must_equal false + end + end + end + + describe "relationships" do + it "has a product" do + b = order_products(:one) + a = products(:mermaid_fin) + + b.must_respond_to :product + b.product.must_equal a + b.product_id.must_equal a.id + end + + it "has an order" do + b = order_products(:one) + a = orders(:order) + + b.must_respond_to :order + b.order.must_equal a + b.order_id.must_equal a.id + end + end +end diff --git a/test/models/order_test.rb b/test/models/order_test.rb new file mode 100644 index 0000000000..45c691a0d2 --- /dev/null +++ b/test/models/order_test.rb @@ -0,0 +1,83 @@ +require "test_helper" + +describe Order do + let(:bad_order) { Order.new( + merchant_id: :grace, + cust_name: "John", + cust_cc: "12345", + cust_cc_exp: "11/22", + cust_addr: "Sea World", + cust_email: "forkhair@mermaid.com", + status: "") + } + + describe "validations" do + it "can be created with valid data" do + orders(:order).must_be :valid? + end + it "won't be valid without status" do + bad_order.wont_be :valid? + bad_order.errors.messages.must_include :status + end + it "won't be valid without cust_name" do + bad_order.status = "complete" + bad_order.cust_name = "" + bad_order.wont_be :valid? + bad_order.errors.messages.must_include :cust_name + end + it "won't be valid without :cust_cc" do + bad_order.cust_cc = "" + bad_order.wont_be :valid? + bad_order.errors.messages.must_include :cust_cc + end + it "won't be valid without :cust_cc_exp" do + bad_order.cust_cc_exp = "" + bad_order.wont_be :valid? + bad_order.errors.messages.must_include :cust_cc_exp + end + it "won't be valid without :cust_addr" do + bad_order.cust_addr = "" + bad_order.wont_be :valid? + bad_order.errors.messages.must_include :cust_addr + end + it "won't be valid without :cust_name" do + bad_order.cust_name = "" + bad_order.wont_be :valid? + bad_order.errors.messages.must_include :cust_name + end + it "won't be valid without :cust_email" do + bad_order.cust_email = "" + bad_order.wont_be :valid? + bad_order.errors.messages.must_include :cust_email + end + it "won't be valid without :cust_email" do + bad_order.cust_email = "" + bad_order.wont_be :valid? + bad_order.errors.messages.must_include :cust_email + end + it "won't be valid if email format is bad" do + bad_order.cust_email = "Email" + bad_order.wont_be :valid? + bad_order.errors.messages.must_include :cust_email + end + end + describe "relationships" do + describe "order products" do + it "contains an array of Order Products" do + orders(:order).order_products.each do |order_product| + order_product.must_be_kind_of OrderProduct + end + end + it "contains an array of Products" do + orders(:order).products.each do |product| + product.must_be_kind_of Product + end + end + end + describe "Merchant relationship" do + it "can have a Merchant" do + orders(:order).must_respond_to :merchant + end + end + end +end diff --git a/test/models/product_test.rb b/test/models/product_test.rb new file mode 100644 index 0000000000..c0c617f0f6 --- /dev/null +++ b/test/models/product_test.rb @@ -0,0 +1,160 @@ +require "test_helper" + +describe Product do + let(:product) { products(:mermaid_fin) } + + describe 'validations' do + it 'accepts valid data' do + product.must_be :valid? + end + + describe 'name' do + it 'requires name to be present' do + product.name = nil + + product.wont_be :valid? + product.errors.messages.must_include :name + end + + it 'requires name to be unique' do + prod2 = Product.new( + name: 'Mermaid Fin', + price: 1.11, + quantity: 10, + merchant_id: merchants(:grace).id + ) + + prod2.wont_be :valid? + prod2.errors.messages.must_include :name + end + end + + describe 'price' do + it 'requires price to be present' do + product.price = nil + + product.wont_be :valid? + product.errors.messages.must_include :price + end + + it 'requires price to be a number greater than 0' do + # Rails automatically converts most invalid inputs to 0. This input would not be accepted. + # Potential issues: 'true' converts to 1 + # Does this require a custom validation? Is there a scenario in which a user could accidentally enter 'true'? + [-15, 'hey', false, nil, 0].each do |value| + product.price = value + + product.wont_be :valid? + product.errors.messages.must_include :price + end + end + end + + describe 'quantity' do + it 'requires quantity to be present' do + product.quantity = nil + + product.wont_be :valid? + product.errors.messages.must_include :quantity + end + + it 'requires quantity to be an integer greater than or equal to 0' do + # Rails automatically converts most invalid inputs to 0. An item won't display with a quantity of 0, but it will save, which isn't ideal. + # Other potential issues: 'true' converts to 1, any float is just rounded down + # Does this require a custom validation? + product.quantity = -1 + + product.wont_be :valid? + product.errors.messages.must_include :quantity + end + end + end + + describe 'relationships' do + it 'has order_products' do + product.must_respond_to :order_products + product.order_products.each do |op| + op.must_be_kind_of OrderProduct + end + end + + it 'has reviews' do + product.must_respond_to :reviews + product.reviews.each do |review| + review.must_be_kind_of Review + end + end + + it 'has orders' do + product.must_respond_to :orders + product.orders do |order| + order.must_be_kind_of Order + end + end + + it 'has merchant' do + product.must_respond_to :merchant + product.merchant.must_be_kind_of Merchant + end + end + + describe 'most_popular' do + it 'returns an array of 6 products if database contains at least 6 products' do + 1..6.times do |i| + Product.create!(name: i, price: i+1, quantity: i, merchant_id: Merchant.first.id) + end + prods = Product.most_popular + + prods.length.must_equal 6 + end + + it 'returns a shorter array of products if database less than 6 products' do + if Product.count >= 6 + i = Product.count + until i < 6 + Product.last.destroy + end + end + prods = Product.most_popular + + prods.length.must_equal Product.count + end + + it 'orders array by number of orders for product' do + prods = Product.most_popular + prods.each_cons(2) do |pair| + (pair[0].orders.count >= pair[1].orders.count).must_equal true + end + end + + it 'returns an empty array if database contains no products' do + Product.destroy_all + + Product.most_popular.must_equal [] + end + end + + describe 'add_categories_by_params' do + it 'adds provided categories to product' do + test_categories = [Category.first, Category.last] + test_params = { 'category_test_0' => test_categories[0].id, + 'category_test_1' => test_categories[1].id } + + product.add_categories_by_params(test_params) + + product.categories.must_include test_categories[0] + product.categories.must_include test_categories[1] + end + + it 'wont duplicate categories within product' do + test_category = Category.first + test_params = { 'category_test' => test_category.id } + + count = product.categories.count + + test_params = { 'category_test' => test_category.id } + + product.categories.count.must_equal count + end + end +end diff --git a/test/models/review_test.rb b/test/models/review_test.rb new file mode 100644 index 0000000000..00f5369328 --- /dev/null +++ b/test/models/review_test.rb @@ -0,0 +1,79 @@ +require "test_helper" + +describe Review do + let(:review) { reviews(:review) } + + describe "validations" do + it "will return false without a rating" do + review.rating = nil + review.valid?.must_equal false + end + + it "will return false if rating is not an integer" do + review.rating = 3.1 + review.valid?.must_equal false + end + + it "will return false if rating is not in range of 1-5" do + review.rating = 6 + review.valid?.must_equal false + + review.rating = 0 + review.valid?.must_equal false + end + end + + describe "relations" do + it 'belongs to merchants' do + Review.reflect_on_association(:merchant) + end + + it "valid even if it does not belong to a merchant" do + review.merchant = nil + review.valid?.must_equal true + end + + it 'belongs to products' do + Review.reflect_on_association(:product) + end + + it "invalid if it has no product" do + review.product_id = nil + review.valid?.must_equal false + end + end + + describe "get_rating_stars" do + it 'returns 1 star if given a rating of 0-1' do + Review.get_rating_stars(0).must_equal "⭑⭒⭒⭒⭒" + Review.get_rating_stars(0.5).must_equal "⭑⭒⭒⭒⭒" + Review.get_rating_stars(1.0).must_equal "⭑⭒⭒⭒⭒" + end + + it 'returns 2 star if given a rating of 1-2' do + Review.get_rating_stars(1.5).must_equal "⭑⭑⭒⭒⭒" + Review.get_rating_stars(2).must_equal "⭑⭑⭒⭒⭒" + end + + it 'returns 3 star if given a rating of 2-3' do + Review.get_rating_stars(2.5).must_equal "⭑⭑⭑⭒⭒" + Review.get_rating_stars(3).must_equal "⭑⭑⭑⭒⭒" + end + + it 'returns 4 star if given a rating of 3-4' do + Review.get_rating_stars(3.5).must_equal "⭑⭑⭑⭑⭒" + Review.get_rating_stars(4).must_equal "⭑⭑⭑⭑⭒" + end + + it 'returns 5 star if given a rating of greater than 4' do + Review.get_rating_stars(4.5).must_equal "⭑⭑⭑⭑⭑" + Review.get_rating_stars(5).must_equal "⭑⭑⭑⭑⭑" + Review.get_rating_stars(6).must_equal "⭑⭑⭑⭑⭑" + end + + it 'returns empty string if rating is nil or empty' do + Review.get_rating_stars("").must_equal "" + Review.get_rating_stars(nil).must_equal "" + end + end +end diff --git a/test/system/.keep b/test/system/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000000..6b7f7c378f --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,57 @@ +require 'simplecov' +SimpleCov.start +ENV["RAILS_ENV"] = "test" +require File.expand_path("../../config/environment", __FILE__) +require "rails/test_help" +require "minitest/rails" +require "minitest/reporters" # for Colorized output + +# For colorful output! +Minitest::Reporters.use!( + Minitest::Reporters::SpecReporter.new, + ENV, + Minitest.backtrace_filter +) + + +# To add Capybara feature tests add `gem "minitest-rails-capybara"` +# to the test group in the Gemfile and uncomment the following: +# require "minitest/rails/capybara" + +# Uncomment for awesome colorful output +# require "minitest/pride" + +class ActiveSupport::TestCase + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. + fixtures :all + # Add more helper methods to be used by all tests here... + + def setup + # Once you have enabled test mode, all requests + # to OmniAuth will be short circuited to use the mock authentication hash. + # A request to /auth/provider will redirect immediately to /auth/provider/callback. + OmniAuth.config.test_mode = true + end + + def mock_auth_hash(user) + return { + provider: user.oauth_provider, + uid: user.oauth_uid, + info: { + email: user.email, + nickname: user.username + } + } + end + + def login_test_user + user = merchants(:ada) + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(user)) + get login_path(:github) + end + + def logout_test_user + OmniAuth.config.mock_auth[:github] = nil + get logout_path + end +end diff --git a/tmp/.keep b/tmp/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/.keep b/vendor/.keep new file mode 100644 index 0000000000..e69de29bb2