diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE index cfd5939478..c39592e8ab 100644 --- a/.github/PULL_REQUEST_TEMPLATE +++ b/.github/PULL_REQUEST_TEMPLATE @@ -4,12 +4,12 @@ Congratulations! You're submitting your assignment! These comprehension question ## Comprehension Questions Question | Answer :------------- | :------------- -How did your team break up the work to be done? | -How did your team utilize git to collaborate? | -What did your group do to try to keep your code DRY while many people collaborated on it? | -What was a technical challenge that you faced as a group? | -What was a team/personal challenge that you faced as a group? | -What could your team have done better? | -What was your application's ERD? (include a link) | -What is your Trello URL? | -What is the Heroku URL of your deployed application? | +How did your team break up the work to be done? | We divided work by MVC. We have one Model/DB person, one View person, and two Controller people. Most of the individual work were done at home. Whenever there was a need to collaborate or communicate, we did it at Ada as a whole group or pairs. +How did your team utilize git to collaborate? | All of us had our own branches on local and/or github, especially for features we were not sure about. We learned about how to resolve merge conflicts, and realized that no merge conflicts doesn't mean no errors during merging. Although we planned to use PR in our project, we didn't try it because of time constraint. We should definitely use it in our future group project. +What did your group do to try to keep your code DRY while many people collaborated on it? | It's hard... We kinda prioritize our work to make the app work, rather than writing beautiful codes. We did some cleanup when the app was 90% ready for demo. We probably will do more after final deployment. +What was a technical challenge that you faced as a group? | Cart logic. After discussions with tutor and PM, we realized that there were several ways to implement it. We compared them and chose one based on how comfortable we were with the different options. It still took us a long time to implement that function. +What was a team/personal challenge that you faced as a group? | We believed that all members in the team did their best to make our app not just work but better than we expected. +What could your team have done better? | We probably should write controller tests first next time. +What was your application's ERD? (include a link) | https://www.lucidchart.com/documents/edit/619b0172-f884-42d1-91fe-ea00101d45c1/0 +What is your Trello URL? | https://trello.com/b/2tpzc7EV/puppsy +What is the Heroku URL of your deployed application? | https://puppsy.herokuapp.com/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..8a800278b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# 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 + +.env +.byebug_history + +.DS_Store +coverage + +.env diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000000..e94a42424a --- /dev/null +++ b/Gemfile @@ -0,0 +1,80 @@ +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.6' +# Use postgresql as the database for Active Record +gem 'pg', '>= 0.18', '< 2.0' +# 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 + +# 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', '~> 4.0' +# Use ActiveModel has_secure_password +# gem 'bcrypt', '~> 3.1.7' + +# 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' +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' +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 'jquery-rails' +gem 'foundation-rails' +gem 'normalize-rails' +group :development, :test do + gem 'pry-rails' +end + +group :development do + gem 'better_errors' + gem 'binding_of_caller' + gem 'dotenv-rails' +end + +group :test do + gem 'minitest-rails' + gem 'minitest-reporters' + gem 'simplecov', require: false +end + + +gem "omniauth" +gem "omniauth-github" + +gem 'client_side_validations' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000000..0cb6d30b5b --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,280 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.1.6) + actionpack (= 5.1.6) + nio4r (~> 2.0) + websocket-driver (~> 0.6.1) + actionmailer (5.1.6) + actionpack (= 5.1.6) + actionview (= 5.1.6) + activejob (= 5.1.6) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.1.6) + actionview (= 5.1.6) + activesupport (= 5.1.6) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.1.6) + activesupport (= 5.1.6) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.1.6) + activesupport (= 5.1.6) + globalid (>= 0.3.6) + activemodel (5.1.6) + activesupport (= 5.1.6) + activerecord (5.1.6) + activemodel (= 5.1.6) + activesupport (= 5.1.6) + arel (~> 8.0) + activesupport (5.1.6) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + 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.8.0) + debug_inspector (>= 0.0.1) + builder (3.2.3) + byebug (10.0.2) + capybara (2.18.0) + addressable + mini_mime (>= 0.1.3) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (>= 2.0, < 4.0) + childprocess (0.9.0) + ffi (~> 1.0, >= 1.0.11) + client_side_validations (11.1.2) + jquery-rails (~> 4.3) + js_regex (~> 2.2) + rails (>= 5.0.0.1, <= 6.0) + coderay (1.1.2) + concurrent-ruby (1.0.5) + crass (1.0.4) + debug_inspector (0.0.3) + docile (1.3.0) + dotenv (2.3.0) + dotenv-rails (2.3.0) + dotenv (= 2.3.0) + railties (>= 3.2, < 6.0) + erubi (1.7.1) + execjs (2.7.0) + faraday (0.12.2) + multipart-post (>= 1.2, < 3) + ffi (1.9.23) + foundation-rails (6.4.3.0) + railties (>= 3.1.0) + sass (>= 3.3.0, < 3.5) + sprockets-es6 (>= 0.9.0) + globalid (0.4.1) + activesupport (>= 4.2.0) + hashie (3.5.7) + i18n (1.0.1) + concurrent-ruby (~> 1.0) + jbuilder (2.7.0) + activesupport (>= 4.2.0) + multi_json (>= 1.2) + jquery-rails (4.3.3) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + jquery-turbolinks (2.1.0) + railties (>= 3.1.0) + turbolinks + js_regex (2.2.1) + regexp_parser (>= 0.4.11, <= 0.5.0) + json (2.1.0) + 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.2.2) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.0) + mini_mime (>= 0.1.1) + method_source (0.9.0) + mini_mime (1.0.0) + mini_portile2 (2.3.0) + minitest (5.11.3) + minitest-rails (3.0.0) + minitest (~> 5.8) + railties (~> 5.0) + minitest-reporters (1.2.0) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + multi_json (1.13.1) + multi_xml (0.6.0) + multipart-post (2.0.0) + nio4r (2.3.0) + nokogiri (1.8.2) + mini_portile2 (~> 2.3.0) + normalize-rails (4.1.1) + 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.8.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.5.0) + oauth2 (~> 1.1) + omniauth (~> 1.2) + pg (1.0.0) + pry (0.11.3) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry-rails (0.3.6) + pry (>= 0.10.4) + public_suffix (3.0.2) + puma (3.11.4) + rack (2.0.4) + rack-test (1.0.0) + rack (>= 1.0, < 3) + rails (5.1.6) + actioncable (= 5.1.6) + actionmailer (= 5.1.6) + actionpack (= 5.1.6) + actionview (= 5.1.6) + activejob (= 5.1.6) + activemodel (= 5.1.6) + activerecord (= 5.1.6) + activesupport (= 5.1.6) + bundler (>= 1.3.0) + railties (= 5.1.6) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (5.1.6) + actionpack (= 5.1.6) + activesupport (= 5.1.6) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (12.3.1) + rb-fsevent (0.10.3) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + regexp_parser (0.4.13) + ruby-progressbar (1.9.0) + ruby_dep (1.5.0) + rubyzip (1.2.1) + sass (3.4.25) + sass-rails (5.0.7) + 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.11.0) + childprocess (~> 0.5) + rubyzip (~> 1.2) + simplecov (0.16.1) + docile (~> 1.1) + 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.1.1) + turbolinks-source (~> 5.1) + turbolinks-source (5.1.0) + tzinfo (1.2.5) + thread_safe (~> 0.1) + uglifier (4.1.10) + execjs (>= 0.3.0, < 3) + web-console (3.6.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.3) + xpath (3.0.0) + nokogiri (~> 1.8) + +PLATFORMS + ruby + +DEPENDENCIES + better_errors + binding_of_caller + byebug + capybara (~> 2.13) + client_side_validations + dotenv-rails + foundation-rails + jbuilder (~> 2.5) + jquery-rails + jquery-turbolinks + listen (>= 3.0.5, < 3.2) + minitest-rails + minitest-reporters + normalize-rails + omniauth + omniauth-github + pg (>= 0.18, < 2.0) + pry-rails + puma (~> 3.7) + rails (~> 5.1.6) + 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) + +BUNDLED WITH + 1.16.1 diff --git a/README.md b/README.md index 2758975bf5..8c9f25b6ab 100644 --- a/README.md +++ b/README.md @@ -203,3 +203,9 @@ This project is due EOD Apr 27 via PR against Ada-C9/betsy. ## What Instructors Are Looking For Check out the [feedback template](feedback.md) which lists the items instructors will be looking for as they evaluate your project. + + + +CODE PARKING LOT: + + <%= button_to "PAY AND PLACE ORDER", {:action =>"update_to_paid", :controller => "cart" }, method: :patch, class: "button" %> 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/1.png b/app/assets/images/1.png new file mode 100644 index 0000000000..ffccfd1943 Binary files /dev/null and b/app/assets/images/1.png differ diff --git a/app/assets/images/10.jpg b/app/assets/images/10.jpg new file mode 100644 index 0000000000..44c89dd6e3 Binary files /dev/null and b/app/assets/images/10.jpg differ diff --git a/app/assets/images/11.jpg b/app/assets/images/11.jpg new file mode 100644 index 0000000000..5d2fa18247 Binary files /dev/null and b/app/assets/images/11.jpg differ diff --git a/app/assets/images/12.jpg b/app/assets/images/12.jpg new file mode 100644 index 0000000000..b67541bd58 Binary files /dev/null and b/app/assets/images/12.jpg differ diff --git a/app/assets/images/13.jpg b/app/assets/images/13.jpg new file mode 100644 index 0000000000..59a7b4ab85 Binary files /dev/null and b/app/assets/images/13.jpg differ diff --git a/app/assets/images/14.jpg b/app/assets/images/14.jpg new file mode 100644 index 0000000000..7618f7d92d Binary files /dev/null and b/app/assets/images/14.jpg differ diff --git a/app/assets/images/15.jpg b/app/assets/images/15.jpg new file mode 100644 index 0000000000..9cb995e412 Binary files /dev/null and b/app/assets/images/15.jpg differ diff --git a/app/assets/images/16.jpg b/app/assets/images/16.jpg new file mode 100644 index 0000000000..d83aa74520 Binary files /dev/null and b/app/assets/images/16.jpg differ diff --git a/app/assets/images/17.jpg b/app/assets/images/17.jpg new file mode 100644 index 0000000000..d531d07d35 Binary files /dev/null and b/app/assets/images/17.jpg differ diff --git a/app/assets/images/2.png b/app/assets/images/2.png new file mode 100644 index 0000000000..ad4587d7e2 Binary files /dev/null and b/app/assets/images/2.png differ diff --git a/app/assets/images/3.jpg b/app/assets/images/3.jpg new file mode 100644 index 0000000000..97032b212e Binary files /dev/null and b/app/assets/images/3.jpg differ diff --git a/app/assets/images/4.jpg b/app/assets/images/4.jpg new file mode 100644 index 0000000000..01cf1456f6 Binary files /dev/null and b/app/assets/images/4.jpg differ diff --git a/app/assets/images/5.jpg b/app/assets/images/5.jpg new file mode 100644 index 0000000000..5a151ab5c0 Binary files /dev/null and b/app/assets/images/5.jpg differ diff --git a/app/assets/images/6.jpg b/app/assets/images/6.jpg new file mode 100644 index 0000000000..cc81723be2 Binary files /dev/null and b/app/assets/images/6.jpg differ diff --git a/app/assets/images/7.png b/app/assets/images/7.png new file mode 100644 index 0000000000..02e5ad40a6 Binary files /dev/null and b/app/assets/images/7.png differ diff --git a/app/assets/images/8.png b/app/assets/images/8.png new file mode 100644 index 0000000000..5bd0f1e7d9 Binary files /dev/null and b/app/assets/images/8.png differ diff --git a/app/assets/images/9.png b/app/assets/images/9.png new file mode 100644 index 0000000000..cc4550d518 Binary files /dev/null and b/app/assets/images/9.png differ diff --git a/app/assets/images/dog-1.jpg b/app/assets/images/dog-1.jpg new file mode 100644 index 0000000000..37b83da6eb Binary files /dev/null and b/app/assets/images/dog-1.jpg differ diff --git a/app/assets/images/dog-2.jpg b/app/assets/images/dog-2.jpg new file mode 100644 index 0000000000..1b34373dbd Binary files /dev/null and b/app/assets/images/dog-2.jpg differ diff --git a/app/assets/images/dog-3.jpg b/app/assets/images/dog-3.jpg new file mode 100644 index 0000000000..0ceeae54c7 Binary files /dev/null and b/app/assets/images/dog-3.jpg differ diff --git a/app/assets/images/dog-4.jpg b/app/assets/images/dog-4.jpg new file mode 100644 index 0000000000..64c7f4c9db Binary files /dev/null and b/app/assets/images/dog-4.jpg differ diff --git a/app/assets/images/dog-5.jpg b/app/assets/images/dog-5.jpg new file mode 100644 index 0000000000..248048e801 Binary files /dev/null and b/app/assets/images/dog-5.jpg differ diff --git a/app/assets/images/dog-with-keys.png b/app/assets/images/dog-with-keys.png new file mode 100644 index 0000000000..3013e014c4 Binary files /dev/null and b/app/assets/images/dog-with-keys.png differ diff --git a/app/assets/images/favicon.png b/app/assets/images/favicon.png new file mode 100644 index 0000000000..a7e1171dd9 Binary files /dev/null and b/app/assets/images/favicon.png differ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 0000000000..a0e7aa5784 --- /dev/null +++ b/app/assets/javascripts/application.js @@ -0,0 +1,22 @@ +// 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 jquery + +// +//= require rails-ujs +//= require foundation +//= require turbolinks +//= require_tree . + +//= require rails.validations + +$(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/cart.js b/app/assets/javascripts/cart.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/cart.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/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/homepage.js b/app/assets/javascripts/homepage.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/homepage.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_items.js b/app/assets/javascripts/order_items.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/order_items.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/rails.validations.js b/app/assets/javascripts/rails.validations.js new file mode 100644 index 0000000000..661990d961 --- /dev/null +++ b/app/assets/javascripts/rails.validations.js @@ -0,0 +1,584 @@ + +/*! + * Client Side Validations - v11.1.2 (https://github.com/DavyJonesLocker/client_side_validations) + * Copyright (c) 2018 Geremia Taglialatela, Brian Cardarella + * Licensed under MIT (http://opensource.org/licenses/mit-license.php) + */ + +(function() { + var $, ClientSideValidations, initializeOnEvent, validateElement, validateForm, validatorsFor, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + $ = jQuery; + + $.fn.disableClientSideValidations = function() { + ClientSideValidations.disable(this); + return this; + }; + + $.fn.enableClientSideValidations = function() { + this.filter(ClientSideValidations.selectors.forms).each(function() { + return ClientSideValidations.enablers.form(this); + }); + this.filter(ClientSideValidations.selectors.inputs).each(function() { + return ClientSideValidations.enablers.input(this); + }); + return this; + }; + + $.fn.resetClientSideValidations = function() { + this.filter(ClientSideValidations.selectors.forms).each(function() { + return ClientSideValidations.reset(this); + }); + return this; + }; + + $.fn.validate = function() { + this.filter(ClientSideValidations.selectors.forms).each(function() { + return $(this).enableClientSideValidations(); + }); + return this; + }; + + $.fn.isValid = function(validators) { + var obj; + obj = $(this[0]); + if (obj.is('form')) { + return validateForm(obj, validators); + } else { + return validateElement(obj, validatorsFor(this[0].name, validators)); + } + }; + + validatorsFor = function(name, validators) { + var captures, validator, validator_name; + if (validators.hasOwnProperty(name)) { + return validators[name]; + } + name = name.replace(/\[(\w+_attributes)\]\[[\da-z_]+\](?=\[(?:\w+_attributes)\])/g, '[$1][]'); + if (captures = name.match(/\[(\w+_attributes)\].*\[(\w+)\]$/)) { + for (validator_name in validators) { + validator = validators[validator_name]; + if (validator_name.match("\\[" + captures[1] + "\\].*\\[\\]\\[" + captures[2] + "\\]$")) { + name = name.replace(/\[[\da-z_]+\]\[(\w+)\]$/g, '[][$1]'); + } + } + } + return validators[name] || {}; + }; + + validateForm = function(form, validators) { + var valid; + form.trigger('form:validate:before.ClientSideValidations'); + valid = true; + form.find(ClientSideValidations.selectors.validate_inputs).each(function() { + if (!$(this).isValid(validators)) { + valid = false; + } + return true; + }); + if (valid) { + form.trigger('form:validate:pass.ClientSideValidations'); + } else { + form.trigger('form:validate:fail.ClientSideValidations'); + } + form.trigger('form:validate:after.ClientSideValidations'); + return valid; + }; + + validateElement = function(element, validators) { + var afterValidate, destroyInputName, executeValidators, failElement, local, passElement, remote; + element.trigger('element:validate:before.ClientSideValidations'); + passElement = function() { + return element.trigger('element:validate:pass.ClientSideValidations').data('valid', null); + }; + failElement = function(message) { + element.trigger('element:validate:fail.ClientSideValidations', message).data('valid', false); + return false; + }; + afterValidate = function() { + return element.trigger('element:validate:after.ClientSideValidations').data('valid') !== false; + }; + executeValidators = function(context) { + var fn, i, kind, len, message, ref, valid, validator; + valid = true; + for (kind in context) { + fn = context[kind]; + if (validators[kind]) { + ref = validators[kind]; + for (i = 0, len = ref.length; i < len; i++) { + validator = ref[i]; + if (message = fn.call(context, element, validator)) { + valid = failElement(message); + break; + } + } + if (!valid) { + break; + } + } + } + return valid; + }; + if (element.attr('name').search(/\[([^\]]*?)\]$/) >= 0) { + destroyInputName = element.attr('name').replace(/\[([^\]]*?)\]$/, '[_destroy]'); + if ($("input[name='" + destroyInputName + "']").val() === '1') { + passElement(); + return afterValidate(); + } + } + if (element.data('changed') === false) { + return afterValidate(); + } + element.data('changed', false); + local = ClientSideValidations.validators.local; + remote = ClientSideValidations.validators.remote; + if (executeValidators(local) && executeValidators(remote)) { + passElement(); + } + return afterValidate(); + }; + + ClientSideValidations = { + callbacks: { + element: { + after: function(element, eventData) {}, + before: function(element, eventData) {}, + fail: function(element, message, addError, eventData) { + return addError(); + }, + pass: function(element, removeError, eventData) { + return removeError(); + } + }, + form: { + after: function(form, eventData) {}, + before: function(form, eventData) {}, + fail: function(form, eventData) {}, + pass: function(form, eventData) {} + } + }, + enablers: { + form: function(form) { + var $form, binding, event, ref; + $form = $(form); + form.ClientSideValidations = { + settings: $form.data('clientSideValidations'), + addError: function(element, message) { + return ClientSideValidations.formBuilders[form.ClientSideValidations.settings.html_settings.type].add(element, form.ClientSideValidations.settings.html_settings, message); + }, + removeError: function(element) { + return ClientSideValidations.formBuilders[form.ClientSideValidations.settings.html_settings.type].remove(element, form.ClientSideValidations.settings.html_settings); + } + }; + ref = { + 'submit.ClientSideValidations': function(eventData) { + if (!$form.isValid(form.ClientSideValidations.settings.validators)) { + eventData.preventDefault(); + eventData.stopImmediatePropagation(); + } + }, + 'ajax:beforeSend.ClientSideValidations': function(eventData) { + if (eventData.target === this) { + $form.isValid(form.ClientSideValidations.settings.validators); + } + }, + 'form:validate:after.ClientSideValidations': function(eventData) { + ClientSideValidations.callbacks.form.after($form, eventData); + }, + 'form:validate:before.ClientSideValidations': function(eventData) { + ClientSideValidations.callbacks.form.before($form, eventData); + }, + 'form:validate:fail.ClientSideValidations': function(eventData) { + ClientSideValidations.callbacks.form.fail($form, eventData); + }, + 'form:validate:pass.ClientSideValidations': function(eventData) { + ClientSideValidations.callbacks.form.pass($form, eventData); + } + }; + for (event in ref) { + binding = ref[event]; + $form.on(event, binding); + } + return $form.find(ClientSideValidations.selectors.inputs).each(function() { + return ClientSideValidations.enablers.input(this); + }); + }, + input: function(input) { + var $form, $input, binding, event, form, ref; + $input = $(input); + form = input.form; + $form = $(form); + ref = { + 'focusout.ClientSideValidations': function() { + $(this).isValid(form.ClientSideValidations.settings.validators); + }, + 'change.ClientSideValidations': function() { + $(this).data('changed', true); + }, + 'element:validate:after.ClientSideValidations': function(eventData) { + ClientSideValidations.callbacks.element.after($(this), eventData); + }, + 'element:validate:before.ClientSideValidations': function(eventData) { + ClientSideValidations.callbacks.element.before($(this), eventData); + }, + 'element:validate:fail.ClientSideValidations': function(eventData, message) { + var element; + element = $(this); + ClientSideValidations.callbacks.element.fail(element, message, function() { + return form.ClientSideValidations.addError(element, message); + }, eventData); + }, + 'element:validate:pass.ClientSideValidations': function(eventData) { + var element; + element = $(this); + ClientSideValidations.callbacks.element.pass(element, function() { + return form.ClientSideValidations.removeError(element); + }, eventData); + } + }; + for (event in ref) { + binding = ref[event]; + $input.filter(':not(:radio):not([id$=_confirmation])').each(function() { + return $(this).attr('data-validate', true); + }).on(event, binding); + } + $input.filter(':checkbox').on('change.ClientSideValidations', function() { + $(this).isValid(form.ClientSideValidations.settings.validators); + }); + return $input.filter('[id$=_confirmation]').each(function() { + var confirmationElement, element, ref1, results; + confirmationElement = $(this); + element = $form.find("#" + (this.id.match(/(.+)_confirmation/)[1]) + ":input"); + if (element[0]) { + ref1 = { + 'focusout.ClientSideValidations': function() { + element.data('changed', true).isValid(form.ClientSideValidations.settings.validators); + }, + 'keyup.ClientSideValidations': function() { + element.data('changed', true).isValid(form.ClientSideValidations.settings.validators); + } + }; + results = []; + for (event in ref1) { + binding = ref1[event]; + results.push($("#" + (confirmationElement.attr('id'))).on(event, binding)); + } + return results; + } + }); + } + }, + formBuilders: { + 'ActionView::Helpers::FormBuilder': { + add: function(element, settings, message) { + var form, inputErrorField, label, labelErrorField; + form = $(element[0].form); + if (element.data('valid') !== false && (form.find("label.message[for='" + (element.attr('id')) + "']")[0] == null)) { + inputErrorField = $(settings.input_tag); + labelErrorField = $(settings.label_tag); + label = form.find("label[for='" + (element.attr('id')) + "']:not(.message)"); + if (element.attr('autofocus')) { + element.attr('autofocus', false); + } + element.before(inputErrorField); + inputErrorField.find('span#input_tag').replaceWith(element); + inputErrorField.find('label.message').attr('for', element.attr('id')); + labelErrorField.find('label.message').attr('for', element.attr('id')); + labelErrorField.insertAfter(label); + labelErrorField.find('label#label_tag').replaceWith(label); + } + return form.find("label.message[for='" + (element.attr('id')) + "']").text(message); + }, + remove: function(element, settings) { + var errorFieldClass, form, inputErrorField, label, labelErrorField; + form = $(element[0].form); + errorFieldClass = $(settings.input_tag).attr('class'); + inputErrorField = element.closest("." + (errorFieldClass.replace(/\ /g, "."))); + label = form.find("label[for='" + (element.attr('id')) + "']:not(.message)"); + labelErrorField = label.closest("." + errorFieldClass); + if (inputErrorField[0]) { + inputErrorField.find("#" + (element.attr('id'))).detach(); + inputErrorField.replaceWith(element); + label.detach(); + return labelErrorField.replaceWith(label); + } + } + } + }, + patterns: { + numericality: { + "default": new RegExp('^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$'), + only_integer: new RegExp('^[+-]?\\d+$') + } + }, + selectors: { + inputs: ':input:not(button):not([type="submit"])[name]:visible:enabled', + validate_inputs: ':input:enabled:visible[data-validate]', + forms: 'form[data-client-side-validations]' + }, + validators: { + all: function() { + return $.extend({}, local, remote); + }, + local: { + absence: function(element, options) { + if (!/^\s*$/.test(element.val() || '')) { + return options.message; + } + }, + presence: function(element, options) { + if (/^\s*$/.test(element.val() || '')) { + return options.message; + } + }, + acceptance: function(element, options) { + var ref; + switch (element.attr('type')) { + case 'checkbox': + if (!element.prop('checked')) { + return options.message; + } + break; + case 'text': + if (element.val() !== (((ref = options.accept) != null ? ref.toString() : void 0) || '1')) { + return options.message; + } + } + }, + format: function(element, options) { + var message; + message = this.presence(element, options); + if (message) { + if (options.allow_blank === true) { + return; + } + return message; + } + if (options["with"] && !new RegExp(options["with"].source, options["with"].options).test(element.val())) { + return options.message; + } + if (options.without && new RegExp(options.without.source, options.without.options).test(element.val())) { + return options.message; + } + }, + numericality: function(element, options) { + var $form, CHECKS, check, checkValue, fn, number_format, operator, val; + if (options.allow_blank === true && this.presence(element, { + message: options.messages.numericality + })) { + return; + } + $form = $(element[0].form); + number_format = $form[0].ClientSideValidations.settings.number_format; + val = $.trim(element.val()).replace(new RegExp("\\" + number_format.separator, 'g'), '.'); + if (options.only_integer && !ClientSideValidations.patterns.numericality.only_integer.test(val)) { + return options.messages.only_integer; + } + if (!ClientSideValidations.patterns.numericality["default"].test(val)) { + return options.messages.numericality; + } + CHECKS = { + greater_than: '>', + greater_than_or_equal_to: '>=', + equal_to: '==', + less_than: '<', + less_than_or_equal_to: '<=' + }; + for (check in CHECKS) { + operator = CHECKS[check]; + if (!(options[check] != null)) { + continue; + } + checkValue = !isNaN(parseFloat(options[check])) && isFinite(options[check]) ? options[check] : $form.find("[name*=" + options[check] + "]").length === 1 ? $form.find("[name*=" + options[check] + "]").val() : void 0; + if ((checkValue == null) || checkValue === '') { + return; + } + fn = new Function("return " + val + " " + operator + " " + checkValue); + if (!fn()) { + return options.messages[check]; + } + } + if (options.odd && !(parseInt(val, 10) % 2)) { + return options.messages.odd; + } + if (options.even && (parseInt(val, 10) % 2)) { + return options.messages.even; + } + }, + length: function(element, options) { + var CHECKS, blankOptions, check, fn, message, operator, tokenized_length, tokenizer; + tokenizer = options.js_tokenizer || "split('')"; + tokenized_length = new Function('element', "return (element.val()." + tokenizer + " || '').length")(element); + CHECKS = { + is: '==', + minimum: '>=', + maximum: '<=' + }; + blankOptions = {}; + blankOptions.message = options.is ? options.messages.is : options.minimum ? options.messages.minimum : void 0; + message = this.presence(element, blankOptions); + if (message) { + if (options.allow_blank === true) { + return; + } + return message; + } + for (check in CHECKS) { + operator = CHECKS[check]; + if (!options[check]) { + continue; + } + fn = new Function("return " + tokenized_length + " " + operator + " " + options[check]); + if (!fn()) { + return options.messages[check]; + } + } + }, + exclusion: function(element, options) { + var lower, message, option, ref, upper; + message = this.presence(element, options); + if (message) { + if (options.allow_blank === true) { + return; + } + return message; + } + if (options["in"]) { + if (ref = element.val(), indexOf.call((function() { + var i, len, ref1, results; + ref1 = options["in"]; + results = []; + for (i = 0, len = ref1.length; i < len; i++) { + option = ref1[i]; + results.push(option.toString()); + } + return results; + })(), ref) >= 0) { + return options.message; + } + } + if (options.range) { + lower = options.range[0]; + upper = options.range[1]; + if (element.val() >= lower && element.val() <= upper) { + return options.message; + } + } + }, + inclusion: function(element, options) { + var lower, message, option, ref, upper; + message = this.presence(element, options); + if (message) { + if (options.allow_blank === true) { + return; + } + return message; + } + if (options["in"]) { + if (ref = element.val(), indexOf.call((function() { + var i, len, ref1, results; + ref1 = options["in"]; + results = []; + for (i = 0, len = ref1.length; i < len; i++) { + option = ref1[i]; + results.push(option.toString()); + } + return results; + })(), ref) >= 0) { + return; + } + return options.message; + } + if (options.range) { + lower = options.range[0]; + upper = options.range[1]; + if (element.val() >= lower && element.val() <= upper) { + return; + } + return options.message; + } + }, + confirmation: function(element, options) { + var confirmation_value, value; + value = element.val(); + confirmation_value = $("#" + (element.attr('id')) + "_confirmation").val(); + if (!options.case_sensitive) { + value = value.toLowerCase(); + confirmation_value = confirmation_value.toLowerCase(); + } + if (value !== confirmation_value) { + return options.message; + } + }, + uniqueness: function(element, options) { + var form, matches, name, name_prefix, name_suffix, valid, value; + name = element.attr('name'); + if (/_attributes\]\[\d/.test(name)) { + matches = name.match(/^(.+_attributes\])\[\d+\](.+)$/); + name_prefix = matches[1]; + name_suffix = matches[2]; + value = element.val(); + if (name_prefix && name_suffix) { + form = element.closest('form'); + valid = true; + form.find(":input[name^=\"" + name_prefix + "\"][name$=\"" + name_suffix + "\"]").each(function() { + if ($(this).attr('name') !== name) { + if ($(this).val() === value) { + valid = false; + return $(this).data('notLocallyUnique', true); + } else { + if ($(this).data('notLocallyUnique')) { + return $(this).removeData('notLocallyUnique').data('changed', true); + } + } + } + }); + if (!valid) { + return options.message; + } + } + } + } + }, + remote: {} + }, + disable: function(target) { + var $target; + $target = $(target); + $target.off('.ClientSideValidations'); + if ($target.is('form')) { + return ClientSideValidations.disable($target.find(':input')); + } else { + $target.removeData('valid'); + $target.removeData('changed'); + return $target.filter(':input').each(function() { + return $(this).removeAttr('data-validate'); + }); + } + }, + reset: function(form) { + var $form, key; + $form = $(form); + ClientSideValidations.disable(form); + for (key in form.ClientSideValidations.settings.validators) { + form.ClientSideValidations.removeError($form.find("[name='" + key + "']")); + } + return ClientSideValidations.enablers.form(form); + } + }; + + if ((window.Turbolinks != null) && window.Turbolinks.supported) { + initializeOnEvent = window.Turbolinks.EVENTS != null ? 'page:change' : 'turbolinks:load'; + $(document).on(initializeOnEvent, function() { + return $(ClientSideValidations.selectors.forms).validate(); + }); + } else { + $(function() { + return $(ClientSideValidations.selectors.forms).validate(); + }); + } + + window.ClientSideValidations = ClientSideValidations; + +}).call(this); 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/javascripts/sessions.js b/app/assets/javascripts/sessions.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/sessions.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/users.js b/app/assets/javascripts/users.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/users.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..87f5909e0c --- /dev/null +++ b/app/assets/stylesheets/_settings.scss @@ -0,0 +1,868 @@ +// 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, +); +$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-button-cursor: auto; +$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; +$menu-icons-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-sizes: ( + small: 250px, +); +$offcanvas-vertical-sizes: ( + small: 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: #E87A89; +$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; +$xy-block-grid-max: 8; diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css new file mode 100644 index 0000000000..ffb54a4aa5 --- /dev/null +++ b/app/assets/stylesheets/application.css @@ -0,0 +1,443 @@ +/* + * 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 normalize-rails + + * + *= require_tree . + *= require_self + *= require foundation_and_overrides + + */ + +@import url('https://fonts.googleapis.com/css?family=Chicle|Dosis:300,400,500,600,700'); +/* font-family: 'Chicle', cursive; +font-family: 'Dosis', sans-serif; */ + +/* #1A1F22; brown-black; +#c17317; brown; +#E87A89; pink; */ + +/*--------- global ---------*/ +/********** text **********/ +body, h1, h2, h3, h4, h5, p { + font-family: 'Dosis', sans-serif; + color: #1A1F22; +} + +h3 { + margin-bottom: 1rem; + font-weight: bold; +} + +h4 { + font-weight: 500; + margin: 1rem auto; +} + +p { + font-size: 1.2rem; +} + +.color-text { + color: green; + font-weight: bold; +} + +label { + font-size: 1.2rem; + font-weight: 500; + color: #c17317; +} + +/********** message **********/ +.callout { + border: none; + text-align: center; +} + +.callout .success { + color: green; +} + +.callout .error, .callout .alert { + color: red; +} + +.field_with_errors input { + margin-bottom: 0; +} + +.message { + color: red; + margin: 3px auto; +} + +/********** button **********/ +.button { + font-family: 'Dosis', sans-serif; + font-weight: 500; + font-size: 1.2rem; + padding: 0.8rem; + border-radius: 0.5rem; + background-color: #c17317; + margin-left: 20px; + margin-right: 20px; +} + +.button:hover { + background-color: #E87A89; +} + +table .button { + margin: 0; +} + +/********** image **********/ +img { + height: 300px; + margin-bottom: 20px; +} + +/********** list **********/ +ul { + padding-left: 0; + list-style: none; +} + +/********** table **********/ +thead th, thead td, tfoot th, tfoot td { + text-align: center; + font-size: 1.2rem; +} + +/********** header **********/ +.page-header { + width: 100%; + padding: 25px 20px 20px 20px; + background: #1A1F22; + color: white; +} + +.page-header nav { + display: grid; + grid-template-columns: 1fr 4fr 3fr; + margin-left: 30px; + margin-right: 30px; +} + +.page-header h1 { + width: 200px; +} + +.page-header h1 a { + font-family: 'Chicle', cursive; + color: #c17317; +} + +.nav-button { + margin: 20px 5px; +} + +.float-right { + margin-left: auto; + margin-right: 0; +} + +/********** main **********/ +.main-container { + padding: 1rem 2rem; + text-align: center; + min-height: 50rem; + display: flex; + flex-flow: column nowrap; + align-items: center; +} + +/********** boxes **********/ +.search-container, .filter-container, .status-container, .quantity-container { + display: flex; + flex-flow: row nowrap; + justify-content: space-around; +} + +.search-box, .search-button, .status-box, .status-button, .filter-box, .filter-button, .quantity-box, .quantity-button { + display: inline-block; +} + +.form { + min-width: 200px; +} + +/********** footer **********/ +footer { + text-align: center; +} + +.fureign-keys { + height: 30px; +} + +/*--------- home ---------*/ +.orbit-container img { + margin-bottom: 0px; +} + +.list-container { + display: grid; + grid-template-columns: auto 120px; +} + +.product-lists { + display: flex; + flex-flow: row wrap; + justify-content: center; +} + +.product-lists section { + width: 350px; +} + +/*--------- all-products ---------*/ +.boxes { + display: grid; + grid-template-columns: 1fr 1fr; + width: 90%; + align-items: center; +} + +.search-container { + width: 250px; +} + +.search-box { + width: 120px; +} + +.filter-container { + width: 150px; +} + +.filter { + display: grid; + grid-template-columns: 150px 150px; + grid-gap: 5px; + justify-content: right; +} + +.filter .button { + display: none; +} + +.product-list { + display: flex; + flex-flow: row wrap; + width: 100%; + justify-content: space-around; +} + +.product-list li { + width: 400px; + padding-top: 20px; +} + +/*--------- product ---------*/ +.product-container { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 50px; +} + +.product-details { + min-width: 400px; +} + +/********** review **********/ +.review { + max-width: 400px; +} + +.new-review select { + width: 150px; +} + +/*--------- cart ---------*/ +.cart { + display: grid; + grid-template-columns: 1fr 1fr; +} + +.order-items li { + display: grid; + grid-template-columns: 3fr 4fr; + padding-right: 30px; + width: 600px; + margin-top: 20px; + align-items: center; + border-bottom: 1px solid #f1f1f1; +} + +.order-item-photo img { + height: 200px; +} + +.quantity-box { + width: 80px; +} + +.subtotal-remove-container { + width: 300px; + display: flex; + flex-flow: row nowrap; + justify-content: center; + align-items: center; +} + +.checkout-container { + width: 600px; + display: flex; + flex-flow: column nowrap; + align-items: center; +} + +.checkout-form { + display: grid; + grid-template-columns: 1fr 1fr; + margin-bottom: 10px; + border-left: 1px solid #f1f1f1; +} + +.shipping-info, .billing-info { + width: 250px; + padding-left: 30px; +} + +.cart-button { + display: flex; + flex-flow: row nowrap; +} + +/*--------- account ---------*/ +.status-container { + width: 250px; +} + +/*--------- narrow-screen ---------*/ +@media screen and (max-width: 1050px) { + .page-header nav { + display: flex; + flex-flow: column nowrap; + align-items: center; + } + + .float-right { + display: flex; + flex-flow: row nowrap; + margin: 0px; + } + + .page-header h1 { + font-size: 50px; + } + + /********** cart **********/ + .cart { + grid-template-columns: 1fr 2fr; + } + + .order-items li { + display: flex; + flex-flow: column nowrap; + width: 300px; + } +} + +/*--------- mobile-design ---------*/ +@media only screen and (max-width: 600px) { + + /********** global **********/ + .page-header nav, .nav-button, .float-right, .boxes, .filter, .list-container, .product-container, .cart, .form { + display: flex; + flex-flow: column nowrap; + margin-left: 0; + margin-right: 0; + } + + .main-container { + padding: 0.5rem 0.5rem; + } + + h3, h4 { + font-size: 25px; + margin: 0.5rem auto; + } + + .hidden { + display: none; + } + + /********** home **********/ + .categories { + display: none; + } + + /********** all-products **********/ + .search-container, .filter-container { + width: 300px; + } + + .filter .button { + display: block; + } + + /********** cart **********/ + .cart, .checkout-form { + display: flex; + flex-flow: column nowrap; + align-items: center; + } + + .checkout-container { + padding-left: 0px; + } + + .checkout-form { + border: none; + } + /********** account-page **********/ + .status-container { + width: 100px; + } + + /********** order-details **********/ + table.transpose { + width: 390px; + font-size: 19px; + border: 1px solid #f1f1f1; + } + + .transpose thead { + float: left; + height: 380px; + border: none; + } + + .transpose tbody { + float: right; + height: 380px; + border: none; + } + + .transpose thead th, .transpose tbody td { + display: block; + } + +} diff --git a/app/assets/stylesheets/browserslist b/app/assets/stylesheets/browserslist new file mode 100644 index 0000000000..6019618a9a --- /dev/null +++ b/app/assets/stylesheets/browserslist @@ -0,0 +1,4 @@ +last 2 versions +ie >= 9 +Android >= 2.3 +ios >= 7 diff --git a/app/assets/stylesheets/cart.scss b/app/assets/stylesheets/cart.scss new file mode 100644 index 0000000000..653388aee1 --- /dev/null +++ b/app/assets/stylesheets/cart.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Cart controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/categories.scss b/app/assets/stylesheets/categories.scss new file mode 100644 index 0000000000..42976cbc11 --- /dev/null +++ b/app/assets/stylesheets/categories.scss @@ -0,0 +1,3 @@ +// 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/ diff --git a/app/assets/stylesheets/foundation_and_overrides.scss b/app/assets/stylesheets/foundation_and_overrides.scss new file mode 100644 index 0000000000..ed4c5a0ecf --- /dev/null +++ b/app/assets/stylesheets/foundation_and_overrides.scss @@ -0,0 +1,61 @@ +@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-xy-grid-classes; +//@include foundation-grid; +//@include foundation-flex-grid; +@include foundation-flex-classes; +@include foundation-typography; +@include foundation-forms; +@include foundation-button; +@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-menu; +@include foundation-menu-icon; +@include foundation-drilldown-menu; +@include foundation-dropdown; +@include foundation-dropdown-menu; +@include foundation-responsive-embed; +@include foundation-label; +@include foundation-media-object; +@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; +@include foundation-visibility-classes; +@include foundation-float-classes; + +// 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/homepage.scss b/app/assets/stylesheets/homepage.scss new file mode 100644 index 0000000000..9027e07a04 --- /dev/null +++ b/app/assets/stylesheets/homepage.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the homepage controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/order_items.scss b/app/assets/stylesheets/order_items.scss new file mode 100644 index 0000000000..584862de9b --- /dev/null +++ b/app/assets/stylesheets/order_items.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the OrderItems controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/orders.scss b/app/assets/stylesheets/orders.scss new file mode 100644 index 0000000000..741506954d --- /dev/null +++ b/app/assets/stylesheets/orders.scss @@ -0,0 +1,3 @@ +// 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/ diff --git a/app/assets/stylesheets/products.scss b/app/assets/stylesheets/products.scss new file mode 100644 index 0000000000..bff386e55a --- /dev/null +++ b/app/assets/stylesheets/products.scss @@ -0,0 +1,3 @@ +// 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/ diff --git a/app/assets/stylesheets/reviews.scss b/app/assets/stylesheets/reviews.scss new file mode 100644 index 0000000000..11bbb12cd5 --- /dev/null +++ b/app/assets/stylesheets/reviews.scss @@ -0,0 +1,3 @@ +// 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/ diff --git a/app/assets/stylesheets/sessions.scss b/app/assets/stylesheets/sessions.scss new file mode 100644 index 0000000000..ccb1ed25b2 --- /dev/null +++ b/app/assets/stylesheets/sessions.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Sessions controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/users.scss b/app/assets/stylesheets/users.scss new file mode 100644 index 0000000000..31a2eacb84 --- /dev/null +++ b/app/assets/stylesheets/users.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Users controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ 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..d88abf2271 --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,21 @@ +class ApplicationController < ActionController::Base + protect_from_forgery with: :exception + + before_action :find_user + + def find_user + @user = User.find_by(id: session[:user_id]) + end + + def render_404 + render file: "/public/404.html", status: 404 + end + + def require_login + + if @user.nil? + redirect_to github_login_path + end + end + +end diff --git a/app/controllers/cart_controller.rb b/app/controllers/cart_controller.rb new file mode 100644 index 0000000000..a70f12cbf6 --- /dev/null +++ b/app/controllers/cart_controller.rb @@ -0,0 +1,167 @@ + +class CartController < ApplicationController + + def access_cart + @action = update_cart_info_path + @cart = Order.find_by(id: session[:cart_order_id]) + if @cart.nil? + render :empty_cart and return + else + render "orders/cart" + end + end + + def add_to_cart + if !session[:cart_order_id].nil? + @cart = Order.find_by(id: session[:cart_order_id]) + end + if @cart.nil? + @cart = create_cart + session[:cart_order_id] = @cart.id + end + @product = Product.find_by(id: params[:id]) + if @product.nil? + flash[:status] = :failure + flash[:result_text] = "That product could not be added to your cart" + redirect_to cart_path and return + end + desired_quantity = total_quantity_requested + if desired_quantity > @product.stock + flash[:status] = :failure + flash[:result_text] = "Not enough inventory on-hand to complete your request." + flash[:messages] = @product.errors.messages + else + if @target_item = @cart.order_items.find_by(product_id: params[:id]) + @target_item.quantity = desired_quantity + @target_item.save + else + @order_item = OrderItem.create product_id: @product.id, order_id: @cart.id, quantity: desired_quantity, is_shipped: "false" + end + flash[:status] = :success + flash[:result_text] = "Item added to your cart!" + end + redirect_to cart_path + end + + def update_cart_info + @cart = Order.find_by(id: session[:cart_order_id]) + if @cart && @cart.id == session[:cart_order_id] + @cart.name = params[:order][:name] + @cart.email = params[:order][:email] + @cart.street_address = params[:order][:street_address] + @cart.city = params[:order][:city] + @cart.state = params[:order][:state] + @cart.zip = params[:order][:zip] + @cart.name_cc = params[:order][:name_cc] + @cart.credit_card = params[:order][:credit_card] + @cart.expiry = Date.new params[:order]["expiry(1i)"].to_i, params[:order]["expiry(2i)"].to_i, params[:order]["expiry(3i)"].to_i + @cart.ccv= params[:order][:ccv] + @cart.billing_zip = params[:order][:billing_zip] + if @cart.save + # redirect_to order_path(@order.id) + flash[:status] = :success + flash[:result_text] = "Your order information has been successfully updated!" + redirect_to cart_path and return + else + flash[:status] = :failure + flash[:result_text] = "We were unable to update your order information." + flash[:messages] = @cart.errors.messages + redirect_to cart_path and return + end + else + flash[:status] = :failure + flash[:result_text] = "We were unable to find your cart." + render_404 + end + end + + def update_to_paid + @cart = Order.find_by(id: session[:cart_order_id]) + @cart.confirm + if !@cart.save + flash[:status] = :failure + flash[:result_text] = "We weren't able to process your order. Please double-check the form." + flash[:messages] = @cart.errors.messages + redirect_to cart_path + else + flash[:status] = :success + flash[:result_text] = "Your order has been submitted!" + @order = Order.find_by(id: session[:cart_order_id]) + session[:cart_order_id] = nil + render "orders/confirmation" + end + end + + def destroy + @cart = Order.find_by(id: session[:cart_order_id]) + if @cart.nil? + flash[:status] = :failure + flash[:result_text] = "Unable to remove the items from your cart." + redirect_to cart_path and return + end + if @cart.order_items.count > 0 + @cart.order_items.each do |order_item| + order_item.destroy + end + else + flash[:status] = :failure + flash[:result_text] = "Your cart was already empty!" + end + redirect_to cart_path + end + + def remove_single_item + @cart = Order.find_by(id: session[:cart_order_id]) + @order_item = OrderItem.find_by(id: params[:id]) + if @order_item && @cart && (@order_item.order_id == @cart.id) + @item_name = @order_item.product.name + @order_item.destroy + flash[:status] = :success + flash[:result_text] = "#{@item_name} removed from your cart!" + else + flash[:status] = :failure + flash[:result_text] = "Unable to remove the items from your cart." + if @cart + flash[:errors] = @cart.errors.messages + end + if @order_item + flash[:errors] = @order_item.errors.messages + end + redirect_to cart_path and return + end + if !(@cart.order_items.count > 0) + render :empty_cart and return + else + redirect_to cart_path + end + end + +private + + def create_cart + @cart = Order.new status: "pending" + session[:cart_order_id] = @cart.id + if @cart.save + flash[:status] = :success + flash[:result_text] = "Welcome to the Puppsy shopping experience!" + else + flash[:status] = :failure + flash[:result_text] = "We weren't able to create your shopping cart." + flash[:messages] = @cart.errors.messages + end + return @cart + end + + def total_quantity_requested + @product = Product.find_by(id: params[:id]) + total_quantity = 1 + if @product && @cart && (@cart.order_items.count > 0) + @cart.order_items.each do |order_item| + if order_item.product_id == @product.id + total_quantity += order_item.quantity + end + end + end + return total_quantity + end +end diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb new file mode 100644 index 0000000000..9769ad181e --- /dev/null +++ b/app/controllers/categories_controller.rb @@ -0,0 +1,38 @@ +class CategoriesController < ApplicationController + before_action :require_login + skip_before_action :require_login, only: [:create] + + def index + @categories = Category.all + end + + def new + @category = Category.new + end + + def create + category_id = params[:category][:id] + if category_id + redirect_to category_products_path(category_id) + else + @category = Category.new(category_params) + + if @category.save + flash[:status] = :success + flash[:result_text] = "Successfully created a category for #{@category.name}!" + redirect_to products_path + else + flash[:status] = :failure + flash[:result_text] = "Could not create this category." + flash[:messages] = @category.errors.messages + render :new, status: :bad_request + end + end + end + + private + + def category_params + params.require(:category).permit(:name) + 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/homepage_controller.rb b/app/controllers/homepage_controller.rb new file mode 100644 index 0000000000..ad65e1949c --- /dev/null +++ b/app/controllers/homepage_controller.rb @@ -0,0 +1,10 @@ +class HomepageController < ApplicationController + def index + @popular_products = Product.top_sellers + category_outfit = Category.find_by(name: "Outfit") + @outfits = category_outfit.products + category_food = Category.find_by(name: "Food") + @foods = category_food.products + @categories = Category.all + end +end diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb new file mode 100644 index 0000000000..707c032fa7 --- /dev/null +++ b/app/controllers/order_items_controller.rb @@ -0,0 +1,48 @@ +class OrderItemsController < ApplicationController + before_action :require_login + skip_before_action :require_login, only: [:update] + + + def update + @order_item = OrderItem.find_by(id: params[:id]) + if @order_item.nil? + render_404 and return + else + @order_item.quantity = params[:order_item][:quantity] + if @order_item.save + flash[:status] = :success + flash[:result_text] = "Quantity updated!" + else + flash[:status] = :failure + flash[:result_text] = "Could not add the desired quantity of this item to the cart." + flash[:messages] = @order_item.errors.messages + end + end + redirect_to cart_path + end + + def set_status + @order_item = OrderItem.find_by(id: params[:id]) + if @order_item.nil? + render_404 + else + @order_item.update(is_shipped: true) + is_complete = true + @order_item.order.order_items.each do |order_item| + if order_item.is_shipped == false + is_complete = false + end + end + if is_complete + @order_item.order.update(status: "complete") + end + redirect_back fallback_location: user_path(@order_item.product.user) + end + end + + private + + def order_item_params + params.require(:order_item).permit(:quantity, :is_shipped, :order_id, :product_id) + end +end diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb new file mode 100644 index 0000000000..7b73d78118 --- /dev/null +++ b/app/controllers/orders_controller.rb @@ -0,0 +1,106 @@ +class OrdersController < ApplicationController + before_action :require_login + skip_before_action :require_login, only: [:confirmation] + + # def index + # @orders = Order.all + # end + + # def new + # @order = Order.new + # end + +# #TODO +# def create +# @order = Order.new(order_params) +# if @order.save +# flash[:status] = :success +# flash[:result_text] = "Your order has been made - congratulations!" +# redirect_to order_confirmation_path(@order.id) +# else +# flash[:status] = :failure +# flash[:result_text] = "Something has gone wrong in your orders processing." +# render :new, status: :bad_request +# flash[:messages] = @order.errors.messages +# end +# end + + def confirmation + @order = Order.find_by(id: params[:id]) + if @order.nil? + render_404 + end + end + + def show + @order = Order.find_by(id: params[:id]) + if @order.nil? + render_404 + end + end + + + # def edit + # @order = Order.find_by(id: params[:id]) + # if @order.nil? + # render_404 + # end + # end + + def update + @order = Order.find_by(id: params[:id]) + if @order.nil? + render_404 + else + @order.update_attributes(order_params) + if @order.valid? + @order.save + redirect_to order_path(@order.id) + flash[:status] = :success + flash[:result_text] = "#{@order.name} has been updated" + else + render :show, status: :bad_request + flash[:status] = :failure + flash[:result_text] = "#{@order.name} update has failed" + flash[:messages] = @order.errors.messages + end + end + end + + def cancel + @order = Order.find_by(id: params[:id]) + if @order.nil? + render_404 + else + @order.cancel + if @order.save + flash[:status] = :success + flash[:result_text] = "Your order has been cancelled!" + end + redirect_to products_path + end + end + + + # def destroy + # @order = Order.find_by(id: params[:id]) + # if @order.nil? + # render_404 + # else + # if @order + # @order.order_items.each do |item| + # item.destroy + # end + # @order.destroy + # end + # end + # end + + private + + def order_params + params.require(:order).permit(:status,:name,:email,:street_address,:city,:state,:zip,:name_cc,:credit_card,:expiry,:ccv,:billing_zip) + end + + +end diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb new file mode 100644 index 0000000000..b47966a138 --- /dev/null +++ b/app/controllers/products_controller.rb @@ -0,0 +1,117 @@ +class ProductsController < ApplicationController + before_action :require_login + skip_before_action :require_login, only: [:index, :show] + + + def index + @category = Category.new + @user = User.new + if params[:category_id] + @current_category = Category.find_by(id: params[:category_id]) + @products = @current_category.products + @current_user = nil + elsif params[:user_id] + @current_user = User.find_by(id: params[:user_id]) + @products = @current_user.products + @current_category = nil + elsif params[:term] + @products = Product.search(params[:term]) + else + @products = Product.all + end + end + + def new + @product = Product.new + @product.user = User.find(params[:user_id]) + @action = user_products_path(params[:user_id]) + end + + def create + @product = Product.new(product_params) + @product.price = params[:product][:price].to_i * 100 + @product.user = User.find(session[:user_id]) + unless @product.user_id == session[:user_id] + @product.destroy + end + if @product.save && ( @product.user_id == session[:user_id] ) + flash[:status] = :success + flash[:result_text] = "#{@product.name} has been successfully created!" + redirect_to product_path(@product.id) + else + flash[:status] = :failure + flash[:result_text] = "Could not create this product." + flash[:messages] = @product.errors.messages + render :new, status: :bad_request + if @product + @product.destroy + end + end + end + + def show + @product = Product.find_by(id: params[:id]) + if @product.nil? + render_404 + else + @review = Review.new + @action = product_reviews_path(params[:id]) + end + end + + def edit + @product = Product.find_by(id: params[:id]) + if @product.nil? + render_404 and return + end + if @product.user_id == session[:user_id] + @action = product_path(params[:id]) + else + flash[:status] = :failure + flash[:result_text] = "Merchants are only allowed to modify their own products" + redirect_to root_path + end + end + + def update + @product = Product.find_by(id: params[:id]) + if @product.nil? + render_404 and return + end + if @product.user_id == session[:user_id] + @product.update(product_params) + @product.price = params[:product][:price].to_i * 100 + if @product.save + flash[:status] = :success + flash[:result_text] = "#{@product.name} has been successfully updated!" + redirect_to product_path(@product.id) and return + else + flash[:status] = :failure + flash[:result_text] = "Update failed." + flash[:messages] = @product.errors.messages + render :edit, status: :bad_request and return + end + else + flash[:status] = :failure + flash[:result_text] = "Merchants are only allowed to modify their own products" + redirect_to product_path(@product.id) + end + end + + def set_status + @product = Product.find_by(id: params[:id]) + if @product.nil? + render_404 + else + @product.toggle_is_active + @product.save + redirect_back fallback_location: user_path(@product.user) + end + end + + private + + def product_params + params.require(:product).permit(:name, :is_active, :description, :price, :photo_url, :stock, :user_id, :term, :category_ids => [] ) + end +end diff --git a/app/controllers/reviews_controller.rb b/app/controllers/reviews_controller.rb new file mode 100644 index 0000000000..d34662b00b --- /dev/null +++ b/app/controllers/reviews_controller.rb @@ -0,0 +1,22 @@ +class ReviewsController < ApplicationController + + def create + @review = Review.new(review_params) + @review[:product_id] = params[:product_id] + if @review.save + redirect_to product_path(params[:product_id]) + flash[:status] = :success + flash[:result_text] = "Your comment was saved" + else + render :new, status: :bad_request + flash[:status] = :failure + flash[:result_text] = "Your comment was not saved" + flash[:messages] = @review.errors.messages + end + end + + private + def review_params + params.require(:review).permit(:rating,:content) + end +end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000000..6ba283dd9d --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,52 @@ + +class SessionsController < ApplicationController + before_action :require_login + skip_before_action :require_login, only: [:create] + + def new + @username = User.new + end + + def create + auth_hash = request.env['omniauth.auth'] + if auth_hash[:uid] + @user = User.find_by(uid: auth_hash[:uid], provider: 'github') + if @user.nil? + @user = User.build_from_github(auth_hash) + @user.update_image(request.env["omniauth.auth"]["info"]["image"]) + successful_save = @user.save + if successful_save + flash[:status] = :success + flash[:result_text] = "Successful first login!" + else + flash[:status] = :failure + flash[:result_text] = "An error occurred during User creation." + flash[:messages] = @user.errors.messages + end + else + flash[:status] = :success + flash[:result_text] = "Logged in successfully" + @user.update_image(request.env["omniauth.auth"]["info"]["image"]) + @user.save + end + session[:user_id] = @user.id + session_test = session[:user_id] + else + flash[:status] = :failure + flash[:result_text] = "Logging in through Github not successful" + end + redirect_to root_path + end + + # def index + # @user = User.find(session[:user_id]) # < recalls the value set in a previous request + # end + + def destroy + session[:user_id] = nil + flash[:status] = :success + flash[:result_text] = "Successfully logged out" + redirect_to root_path + end + +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 0000000000..b8c8e3fea8 --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,47 @@ +class UsersController < ApplicationController + before_action :require_login + skip_before_action :require_login, only: [:create] + + def index + @users = User.all + end + + def create + user_id = params[:user][:id] + if user_id + redirect_to user_products_path(user_id) + else + flash[:status] = :failure + flash[:result_text] = "Could not create a new user ID." + # flash[:messages] = @user.errors.messages + end + end + + def show + @user = User.find_by(id: params[:id]) + if @user.nil? + render_404 + else + @user_products = @user.products + # Model method for getting the relevant orders is needed + if params[:term] + @term = params[:term] + @orders = @user.list_orders_by_status(@term) + # raise + @revenue = @user.total_revenue_by_status(@term) + @num_orders = @user.num_orders_by_status(@term) + else + @orders = @user.list_all_orders + @revenue = @user.total_revenue + @num_orders = @user.num_orders + end + end + end + + private + + def user_params + params.require(:user).permit(:username, :email, :uid, :provider, :term ) + end + +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000000..e8bb1b7a71 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,17 @@ +module ApplicationHelper + def price_in_dollar(price) + sprintf('%.2f', (price / 100.00)) + end + + def pretty_date(date) + return date.strftime("%b %e, %Y") + end + + def discard_day(date) + return date.strftime("%m/%Y") + end + + # def display_image(photo_url) + # ["product image"].join.html_safe + # end +end diff --git a/app/helpers/cart_helper.rb b/app/helpers/cart_helper.rb new file mode 100644 index 0000000000..ac02bcd1bf --- /dev/null +++ b/app/helpers/cart_helper.rb @@ -0,0 +1,2 @@ +module CartHelper +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/homepage_helper.rb b/app/helpers/homepage_helper.rb new file mode 100644 index 0000000000..c5bbfe518f --- /dev/null +++ b/app/helpers/homepage_helper.rb @@ -0,0 +1,2 @@ +module HomepageHelper +end diff --git a/app/helpers/order_items_helper.rb b/app/helpers/order_items_helper.rb new file mode 100644 index 0000000000..e197528ae1 --- /dev/null +++ b/app/helpers/order_items_helper.rb @@ -0,0 +1,2 @@ +module OrderItemsHelper +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/helpers/reviews_helper.rb b/app/helpers/reviews_helper.rb new file mode 100644 index 0000000000..682b7b1abc --- /dev/null +++ b/app/helpers/reviews_helper.rb @@ -0,0 +1,2 @@ +module ReviewsHelper +end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb new file mode 100644 index 0000000000..309f8b2eb3 --- /dev/null +++ b/app/helpers/sessions_helper.rb @@ -0,0 +1,2 @@ +module SessionsHelper +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 0000000000..2310a240d7 --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,2 @@ +module UsersHelper +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..dd01aeaecf --- /dev/null +++ b/app/models/category.rb @@ -0,0 +1,12 @@ +class Category < ApplicationRecord + has_and_belongs_to_many :products + validates :name, presence: true, uniqueness: true + before_validation :capitalize_name + + private + def capitalize_name + if !name.nil? + self.name = name.capitalize + end + end +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/order.rb b/app/models/order.rb new file mode 100644 index 0000000000..a52202526a --- /dev/null +++ b/app/models/order.rb @@ -0,0 +1,58 @@ +class Order < ApplicationRecord + has_many :order_items + validates :status, presence: true + validates :name, presence: true, length: { minimum: 3 }, unless: :in_cart? + validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }, unless: :in_cart? + validates :street_address, presence: true, unless: :in_cart? + validates :city, presence: true, unless: :in_cart? + validates :state, presence: true, unless: :in_cart? + validates :zip, presence: true, length: { is: 5 }, unless: :in_cart? + validates :name_cc, presence: true, unless: :in_cart? + validates :credit_card, presence: true, length: { in: 14..19 }, unless: :in_cart? + validates :expiry, presence: true, unless: :in_cart? + validates :ccv, presence: true, length: { in: 3..4 }, unless: :in_cart? + validates :billing_zip, presence: true, length: { is: 5 }, unless: :in_cart? + validate :cc_expiry_cannot_be_in_the_past, unless: :in_cart? + + def cc_expiry_cannot_be_in_the_past + if expiry.present? && expiry < Date.today + errors.add(:expiry, "can't be in the past") + end + end + + # validate :check_atleast_one_order_item, unless: :in_cart? + + # def check_atleast_one_order_item + # if order_items.count < 1 + # errors.add(:order, "need atleast one order item") + # end + # end + + def total_cost + order_items.map { |order_item| order_item.subtotal }.sum.round(2) + end + + def credit_card_last_digits + credit_card[-4..-1] + end + + def can_cancel? + status == "paid" && !order_items.any? { |i| i.is_shipped } + end + + def confirm + self.status = "paid" + self.order_items.each { |item| item.product.update_attributes({:stock => item.product.stock - item.quantity}) } + end + + def cancel + self.status = "cancelled" + self.order_items.each { |item| item.product.update_attributes({:stock => item.product.stock + item.quantity}) } + end + + private + + def in_cart? + status == "pending" + end +end diff --git a/app/models/order_item.rb b/app/models/order_item.rb new file mode 100644 index 0000000000..c2b1c3d56b --- /dev/null +++ b/app/models/order_item.rb @@ -0,0 +1,29 @@ +class OrderItem < ApplicationRecord + belongs_to :order + belongs_to :product + validates :quantity, presence: true, numericality: { only_integer: true, greater_than: 0 } + validate :check_product_is_active, if: :contains_product? + validate :check_quantity, if: :contains_product? + + def check_product_is_active + if !product.is_active + errors.add(:is_active,"product is no longer available") + end + end + + def check_quantity + if quantity.to_i > product.stock + errors.add(:stock, "only #{product.stock} items available") + end + end + + def subtotal + ((quantity * product.price)/100.0).round(2) + end + + private + def contains_product? + !product.nil? + end + +end diff --git a/app/models/product.rb b/app/models/product.rb new file mode 100644 index 0000000000..a25627b7de --- /dev/null +++ b/app/models/product.rb @@ -0,0 +1,29 @@ +class Product < ApplicationRecord + belongs_to :user + has_and_belongs_to_many :categories + has_many :reviews + has_many :order_items + validates :name, presence: true, uniqueness: { case_sensitive: false } + validates :price, presence: true, numericality: { only_integer: true, greater_than: 0 } + validates :stock, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 0} + + def average_rating + (reviews.average(:rating) || 0).round(1) + end + + def toggle_is_active + self.is_active = !is_active + end + + def self.top_sellers(count = 5) + sorted_products = self.all.sort_by { |p| + p.order_items.map { |i| i.quantity }.sum + }.reverse! + actual_count = [count, sorted_products.count].min + return sorted_products[0...actual_count] + end + + def self.search(term) + where('lower(name) LIKE ?', "%#{term.downcase}%").order('id DESC') + end +end diff --git a/app/models/review.rb b/app/models/review.rb new file mode 100644 index 0000000000..86ef794b29 --- /dev/null +++ b/app/models/review.rb @@ -0,0 +1,5 @@ +class Review < ApplicationRecord + belongs_to :product + validates :rating, presence: true, numericality: { only_integer: true } + validates_inclusion_of :rating, in: (1..5) +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000000..d668ea8654 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,48 @@ +class User < ApplicationRecord + has_many :products + has_many :order_items, through: :products + has_many :orders, through: :order_items + + validates :username, presence: true, uniqueness: true + validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }, uniqueness: true + + def self.build_from_github(auth_hash) + return User.new( + username: auth_hash[:info][:nickname], + email: auth_hash[:info][:email], + uid: auth_hash[:uid], + provider: auth_hash[:provider] + ) + end + + def update_image(image) + self.image = image + end + + def num_orders + order_items.map { |order_item| order_item.order }.uniq.count + end + + def total_revenue + order_items.map { |order_item| order_item.subtotal }.sum.round(2) + end + + def num_orders_by_status(status) + order_items.map { |order_item| + order_item.order if order_item.order.status == status }.compact.uniq.count + end + + def total_revenue_by_status(status) + order_items.map { |order_item| + order_item.subtotal if order_item.order.status == status }.compact.sum.round(2) + end + + def list_all_orders + order_items.map { |order_item| order_item.order }.compact.uniq + end + + def list_orders_by_status(status) + order_items.map { |order_item| order_item.order if order_item.order.status == status }.compact.uniq + end + + end diff --git a/app/views/cart/empty_cart.html.erb b/app/views/cart/empty_cart.html.erb new file mode 100644 index 0000000000..0dc326393d --- /dev/null +++ b/app/views/cart/empty_cart.html.erb @@ -0,0 +1,3 @@ +

Cart

+ +

Your cart is empty!

diff --git a/app/views/categories/new.html.erb b/app/views/categories/new.html.erb new file mode 100644 index 0000000000..c6f468d34e --- /dev/null +++ b/app/views/categories/new.html.erb @@ -0,0 +1,10 @@ +

Add a New Category

+ +
+ <%= form_for @category, validate: true do |f| %> + <%= f.label :category_name %> + <%= f.text_field :name %> + + <%= f.submit "Save", class: "button" %> + <% end %> +
diff --git a/app/views/homepage/index.html.erb b/app/views/homepage/index.html.erb new file mode 100644 index 0000000000..b9ece890bd --- /dev/null +++ b/app/views/homepage/index.html.erb @@ -0,0 +1,86 @@ +

Adoptable Puppies

+
+
+ +
+ +
+ +
+
+ + +
+

Outfit

+
    + <% @outfits.each do |outfit| %> + <% if outfit.is_active %> +

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

    + <%= link_to image_tag(outfit.photo_url, alt: outfit.name), product_path(outfit.id) %> + <% end %> + <% end %> +
+
+ +
+

Food

+
    + <% @foods.each do |food| %> + <% if food.is_active %> +

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

    + <%= link_to image_tag(food.photo_url, alt: food.name), product_path(food.id) %> + <% end %> + <% end %> +
+
+
+ +
+

Categories

+ +
+ +
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000000..9c153a258a --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,71 @@ + + + + + + + Puppsy + + <%= stylesheet_link_tag "application" %> + <%= javascript_include_tag "application", 'data-turbolinks-track' => true, 'rails.validations' => true %> + <%= csrf_meta_tags %> + <%= favicon_link_tag 'favicon.png' %> + + + + + + + +
+
+ <% if flash[:result_text] || flash[:status] || flash[:messages] %> +
+ <% flash.each do |name, message| %> + <% if name == "messages" %> +
    + <% flash[:messages].each do |type, message| %> +
  • +
    <%= type %>: <%= message[0] %>
    +
  • + <% end %> +
+ <% elsif name == "status" %> + <% if flash[:status] == "success" %> +
<%= flash[:result_text] %>
+ <% elsif flash[:status] == "failure" %> +
<%= flash[:result_text] %>
+ <% end %> + <% end %> + <% end %> +
+ <% end %> +
+ + <%= 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/orders/cart.html.erb b/app/views/orders/cart.html.erb new file mode 100644 index 0000000000..08b4b5f142 --- /dev/null +++ b/app/views/orders/cart.html.erb @@ -0,0 +1,99 @@ +

Cart

+ +<% if @cart.order_items.count > 0 %> +
+ + +
+ <%= form_for @cart, url: @action, validate: true do |f| %> +
+ +
+

Shipping Information

+ + <%= f.label :name %> + <%= f.text_field :name %> + + <%= f.label :email %> + <%= f.text_field :email %> + + <%= f.label :street_address %> + <%= f.text_field :street_address %> + + <%= f.label :city %> + <%= f.text_field :city %> + + <%= f.label :state %> + <%= f.text_field :state %> + + <%= f.label :zip_code %> + <%= f.text_field :zip %> +
+ +
+

Billing Information

+ + <%= f.label :name_on_card %> + <%= f.text_field :name_cc %> + + <%= f.label :card_number %> + <%= f.text_field :credit_card %> + + <%= f.label :expiration_date %> + <%= f.date_select :expiry, {discard_day: true, start_year: Date.current.year} %> + <%# use code below for day/month/year picker %> + <%# f.date_field :expiry, "data-provide" => 'datepicker' %> + + <%= f.label :security_code %> + <%= f.text_field :ccv %> + + <%= f.label :billing_zip_code %> + <%= f.text_field :billing_zip %> +
+ +
+ <%= f.submit "Save Information", class: "button" %> + <% end %> + +
+ <%= button_to "Empty Cart", {:action =>"destroy", :controller => "cart" }, :data => {:confirm => 'Are you sure you want to delete all your items?'}, method: :delete, class: "button" %> + <% if @cart.name && @cart.name.length > 0 %> + <%= button_to "PLACE ORDER", {:action =>"update_to_paid", :controller => "cart" }, method: :patch, class: "button" %> + <% end %> +
+
+
+<% else %> +

Your cart is empty!

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

Order Confirmation

+ + + + + + + + + + + + + + + + + + +
Order numberTotal priceDate placedStatus
<%= @order.id %>$<%= @order.total_cost %><%= pretty_date(@order.updated_at) %> + <%= @order.status %> +
+ +<% if @order.can_cancel? %> + <%= link_to "Cancel", order_cancel_path(@order.id), method: :put, class: "button" %> +<% end %> + +

Order Items

+ + + + + + + + + + + <% @order.order_items.each do |order_item| %> + + + + + + <% end %> + +
ItemQuantitySubtotal
<%= link_to order_item.product.name, product_path(order_item.product) %><%= order_item.quantity %>$<%= order_item.subtotal %>
diff --git a/app/views/orders/show.html.erb b/app/views/orders/show.html.erb new file mode 100644 index 0000000000..37a1381667 --- /dev/null +++ b/app/views/orders/show.html.erb @@ -0,0 +1,49 @@ +

Order Details

+ + + + + + + + + + + + + + + + + + +
Order numberTotal priceDate placedStatus
<%= @order.id %>$<%= @order.total_cost %><%= pretty_date(@order.updated_at) %><%= @order.status %>
+ +

Customer Information

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
NameEmailStreet AddressCityStateZip CodeLast 4 digits of CardExpiration Date
<%= @order.name %><%= @order.email %><%= @order.street_address %><%= @order.city %><%= @order.state %><%= @order.zip %><%= @order.credit_card_last_digits %><%= discard_day(@order.expiry) %>
diff --git a/app/views/products/edit.html.erb b/app/views/products/edit.html.erb new file mode 100644 index 0000000000..ed1fb60d63 --- /dev/null +++ b/app/views/products/edit.html.erb @@ -0,0 +1,26 @@ +

Update Product

+ +
+ <%= form_for @product, url: @action, validate: true do |f| %> + + <%= f.label :name %> + <%= f.text_field :name %> + + <%= f.label :price %> + <%= f.text_field :price %> + + <%= f.label :description %> + <%= f.text_area :description %> + + <%= f.label :select_a_category %> + <%= f.collection_select :category_ids, Category.order(:name), :id, :name, {}, {multiple: true} %> + + <%= f.label :photo_url %> + <%= f.text_field :photo_url %> + + <%= f.label :stock %> + <%= f.text_field :stock %> + + <%= f.submit "Update", class: "button" %> +
+<% end %> diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb new file mode 100644 index 0000000000..cb200c709c --- /dev/null +++ b/app/views/products/index.html.erb @@ -0,0 +1,74 @@ +

List of Products

+<% if @current_category %> +

Category: <%= @current_category.name %>

+<% end %> +<% if @current_user %> +

Seller: <%= @current_user.username %>

+<% end %> + +
+
+ <%= form_tag(products_path, method: :get) do %> + +
+ <%= submit_tag 'Search', class: "button" %> +
+ <% end %> +
+
+
+ <%= form_for @category do |f| %> +
+ <%= f.select :id, Category.all.map { |category| [category.name, category.id] }, :prompt => 'Select a category' %> +
+
+ <%= f.submit "Sumbit", class: "button" %> +
+ <% end %> +
+
+ <%= form_for @user do |f| %> +
+ <%= f.select :id, User.all.map { |user| [user.username, user.id] }, :prompt => 'Select a seller' %> +
+
+ <%= f.submit "Sumbit", class: "button" %> +
+ <% end %> +
+
+
+ + diff --git a/app/views/products/new.html.erb b/app/views/products/new.html.erb new file mode 100644 index 0000000000..d572562282 --- /dev/null +++ b/app/views/products/new.html.erb @@ -0,0 +1,25 @@ +

Create a New Product

+ +
+ <%= form_for @product, url: @action, validate: true do |f| %> + <%= f.label :name %> + <%= f.text_field :name %> + + <%= f.label :price %> + <%= f.text_field :price, placeholder: "$" %> + + <%= f.label :description %> + <%= f.text_area :description %> + + <%= f.label :select_a_category %> + <%= f.collection_select :category_ids, Category.order(:name), :id, :name, {}, {multiple: true} %> + + <%= f.label :photo_url %> + <%= f.text_field :photo_url %> + + <%= f.label :stock %> + <%= f.text_field :stock %> + + <%= f.submit "Save", class: "button" %> + <% end %> +
diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb new file mode 100644 index 0000000000..1881e785f2 --- /dev/null +++ b/app/views/products/show.html.erb @@ -0,0 +1,51 @@ +

<%= @product.name %>

+

by <%= link_to @product.user.username, user_products_path(@product.user.id) %>

+ +<% if @product.is_active %> +
+
+ <% if @product.photo_url.nil? %> + <%= link_to image_tag('favicon.png', alt: 'No URL in record'), product_path(@product.id) %> + <% else %> + <%= link_to image_tag(@product.photo_url, alt: @product.name), product_path(@product.id) %> + <% end %> +

<%= @product.description %>

+

Price: $<%= price_in_dollar(@product.price) %>

+ <% if @product.stock > 0 %> +

<%= @product.stock %> in stock

+ <%= button_to "Add to Cart", {:action =>"add_to_cart", :controller => "cart", id: @product.id }, method: :post, class: "button" %> + <% else %> +

Out of Stock

+ <% end %> +
+ +
+

Write a Review

+
+ <%= form_for @review, url: @action, validate: true do |f| %> + <%= f.select :rating, (1..5).to_a, :prompt => 'Select a rating'%> + + <%= f.label :comment %> + <%= f.text_area :content %> + + <%= f.submit "Sumbit", class: "button" %> + <% end %> +
+ +

Reviews

+ <% @product.reviews.reverse.each do |review| %> +
+

+ <% review.rating.times do |i| %> + ⭐️ + <% end %> +

+

<%= review.content %>

+
+ <% end %> +
+
+ +<% else %> +

The product has been discontinued by seller.

+<% end %> diff --git a/app/views/reviews/new.html.erb b/app/views/reviews/new.html.erb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb new file mode 100644 index 0000000000..e71ae7f245 --- /dev/null +++ b/app/views/users/show.html.erb @@ -0,0 +1,120 @@ +

User Account Page

+ +
+ <% if @user.image %> + <%= image_tag @user.image %> + <% end %> + <% if @user.username %> +

<%= @user.username %>

+ <% end %> +
+ +
+ <%= link_to "Add Categories", new_category_path, class: "button" %> + <%= link_to "Add Products", new_user_product_path(@user.id), class: "button" %> +
+ +

List of Products

+ + + + + + + + + + + + <% @user.products.each do |product| %> + + + + + + + + + <% end %> + +
NamePriceUpdateRetire
<%= link_to product.name, product_path(product) %>$<%= price_in_dollar(product.price) %> + <% if product.is_active %> + <%= link_to "Update", edit_product_path(product.id), class: "button" %> + <% else %> + Update + <% end %> + + <% if product.is_active %> + <%= link_to "Retire", product_set_status_path(product.id), method: :put, class: "button" %> + <% else %> + <%= link_to "Reactivate", product_set_status_path(product.id), method: :put, class: "button" %> + <% end %> +
+ +

List of Orders

+ + + + + + + + + + + + + + <% @orders.each do |order| %> + <% order.order_items.each do |order_item| %> + <% if order_item.product.user == @user %> + + + + + + + + + + <% end %> + <% end %> + <% end %> + +
Order # +
+ <%= form_tag(user_path(@user), method: :get) do %> +
+ <%= select_tag :term, "".html_safe, :prompt => 'Select a status' %> +
+
+ <%= submit_tag "Submit", class: "button" %> +
+ <% end %> +
+
Order ItemsShipping Status
<%= link_to order.id, order_path(order.id) %><%= order.status %><%= link_to order_item.product.name, product_path(order_item.product.id) %> + <% if order_item.order.status == "pending" || order_item.order.status == "cancelled" %> + N/A + <% elsif order_item.is_shipped %> + Shipped + <% else %> + <%= link_to "Mark Shipped", order_item_set_status_path(order_item.id), method: :put, class: "button" %> + <% end %> +
+ +

Order Statistics

+ + + + + + + + + + + + + + +
Number of OrdersTotal Revenue
<%= @num_orders %>$<%= @revenue %>
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..5badb2fde0 --- /dev/null +++ b/bin/rails @@ -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 +APP_PATH = File.expand_path('../config/application', __dir__) +require_relative '../config/boot' +require 'rails/commands' 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..5f6ca0f9b2 --- /dev/null +++ b/config/application.rb @@ -0,0 +1,25 @@ +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 + + # 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..d6547d475a --- /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..332875eb69 --- /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/client_side_validations.rb b/config/initializers/client_side_validations.rb new file mode 100644 index 0000000000..aed6c829c9 --- /dev/null +++ b/config/initializers/client_side_validations.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true +# ClientSideValidations Initializer + +# Disabled validators +# ClientSideValidations::Config.disabled_validators = [] + +# Uncomment to validate number format with current I18n locale +# ClientSideValidations::Config.number_format_with_locale = true + +# Uncomment the following block if you want each input field to have the validation messages attached. +# +# Note: client_side_validation requires the error to be encapsulated within +# +# +ActionView::Base.field_error_proc = proc do |html_tag, instance| + if html_tag =~ /^